3

I have big problem with understanding multi threading in my application and because of that finding a bug. I've checked I think all possibilities and still I am getting various (sometimes unexpected) errors.

Maybe someone here will be able to advice me, what I should do.

In my project I am using two external libraries:

  • GraphView - provides views for graph drawing
  • EventBus - provides interface for easy communication between app components

As for the app it has the structure like this:

           MainActivity
            /        \
           /          \
        Thread        Fragment
   (ProcessThread)   (GraphFragment)

The idea is that ProcessThread computes data and provides constant stream of values to GraphFragment throught EventBus. In GraphFragment I have one Series required by GraphView.

To update graphs in real time according to the example I need to make a new Runnable so I did one:

private class PlotsRun implements Runnable{

        @Override
        public void run() {
            mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
            counter++;
            mHandler.post(this);
        }
}

and when I start it from fragment's onResume() method everything is working like a charm.

Unfortunately as I've mentioned I am using external data from another thread. To get it inGraphFragment I am using (according to the documentation) onEventMainThread() method.

And in here no matter what I'll do I can't pass data to update my graph in PlotsRun object. So far I've tried:

  • using Queue - add value in onEventMainThread and get in PlotsRun. It turned out that runnable is reading faster than method is able to update queue.
  • creating various buffers - the result is quite this same as with Queue.
  • calling mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100); directly from onEventMainThread - at some point it gets freez.
  • creating onEvent() method inside my runnable and call from there mHandler.post() - it is blocking UI and updates looks like snapshots.
  • using everything mentioned with or without synchronized() block.

What is quite difficult for me to understand is this runnable which is working correctly (at some point).

As it is said on official Android blog you can't update UI from non UI thread. This is why I can't use another thread inside GraphFragment. But when I've checked my runnable it is running on main thread (UI). This is why I can't create infinite while loop there instead have to call mHandler.post(this).

And still it behaves like another thread because it is faster (called more frequently) then onEventMainThread method.

What can I do to be able to update my graphs (or where I should look) using data from ProcessThread?

EDIT1:

Answering on @Matt Wolfe request I am including what I think is the most important part of a code for this problem with all required variable shown how they are declared. It is very simplified example:

MainActivity:

private ProcessThread testThread = new ProcessThread();

 @Override
    protected void onResume() {
        super.onResume();
        testThread.start();
    }


    private class ProcessThread extends Thread{
        private float value = 0f;
        private ReadingsUpdateData updater = new ReadingsUpdateData(values);
        public void run() {
            while(true) {
                value = getRandom();
                updater.setData(value);
                EventBus.getDefault().post(updater);
            }
        }
    }

GraphFragment:

private LineGraphSeries<DataPoint> mSeries1;
    long counter = 0;
    private Queue<ReadingsUpdateData> queue;

    @Override
    public void onResume() {
        super.onResume();
        mTimer2.run();
    }

    public void onEventMainThread(ReadingsUpdateData data){
        synchronized(queue){
            queue.add(data);
        }
    }

    private class PlotsRun implements Runnable{

        @Override
        public void run() {
            if (queue.size()>0) {
                mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100);
                counter++;
            }
            mHandler.post(this);
        }
    }

The if in runnable is added for protection because of this to fast reading problem. But it shouldn't be here because there should always be something (at least I expect that).

One more thing to add - when I put simple Log.d and counting variable inside onEventMainThread it was updating and displaying it's value correctly, but unfortunately logcat isn't main UI.

EDIT2:

This is mainly response for @MattWolfe comment

The mHandler is just variable declared and created in GrapgFragment:

private final Handler mHandler = new Handler();
private Runnable mTimer2;

Yes, that is right I am using mHandler.post() without any delay. I'll try using some delay to see if there is any difference.

What I didn't mention earlier is that the ProcessThread is providing also data to other fragments - don't worry they don't interfere with each other or share any resources. This is why I am using EventBus.

EDIT3:

This a code that I've used as my another idea with another thread in GraphFragment and runOnMainThread method:

private MyThread thread = new MyThread();

    private class MyThread extends Thread {
        Queue<ReadingsUpdateData> inputList;
        ReadingsUpdateData msg;

        public MyThread() {
            inputList = new LinkedList<>();
        }

        public void run() {
            while(true) {
                try{
                    msg = inputList.poll();
                } catch(NoSuchElementException nse){
                    continue;
                }
                if (msg == null) {
                    continue;
                }
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
                        counter++;
                    }
                });
            }
        }

        public void onEvent(ReadingsUpdateData data){
            inputList.add(data);
        }
    }

Unfortunately, it isn't working neither.

4

4 回答 4

3

首先,

您后面示例中的可运行部分只是为了动画实时数据更新,您可以选择调用appendData()而不创建新的可运行。你需要appendData()从主线程调用。

第二,

您可以appendData()直接从您的函数调用该函数onEventMainThread,但正如您指出的那样,这种方法有时会挂起 UI,这种行为的一个可能原因是您可能过于频繁地发布事件,过于频繁地更新 UI 最终会在某些时候挂起 UI观点。您可以执行以下操作来避免这种情况:

过于频繁地更新 UI 也可能会导致 UI 挂起,这是一个解决方案:

放入一些逻辑ProcessThread以保存上次发送的事件时间并在发送新事件之前进行比较,如果差异小于 1 秒,则保存以供稍后发送,当下一次计算完成时,再次比较时间,如果大于现在超过 1 秒,而不是发送数组中的事件,或者可能只发送最新的事件,因为最新的计算可以代表图的最新状态,对吗?

希望有帮助!

编辑:(响应评论 1 和 2)

我不确定您尝试发布的更新代码是否会提供更好的主意。但我认为您尝试onEventMainThread在可运行或PlotsRun可运行中实现时间检查功能,对吗?如果是,那恐怕对您没有太大帮助。相反,您需要做的是在 ProcessThread 内实现这次检查检查,并且仅在达到阈值时间时才发布新事件。出于以下原因:

1-后端的EventBus会自动创建一个新的runnable并onEventMainThread在其中调用。因此,在内部处理时间检查ProcessThread会在内存中产生更少不需要的可运行文件,从而减少内存消耗。

2-也不需要维护队列和产生新的可运行文件,只需更新onEventMainThread.

以下是仅提供概念证明的最低限度代码,您需要根据需要对其进行更新:

ProcessThread班级:

private class ProcessThread extends Thread{
    private static final long TIME_THRESHOLD = 100; //100 MS but can change as desired
    private long lastSentTime = 0;
    private float value = 0f;
    private ReadingsUpdateData updater = new ReadingsUpdateData(values);
    public void run() {
        while(true) {
            if (System.currentTimeMillis() - lastSentTime < TIME_THRESHOLD) {
                try {
                    Thread.sleep(TIME_THRESHOLD - (System.currentTimeMillis() - lastSentTime));
                } catch (InterruptedException e) {}
            }

            value = getRandom();
            updater.setData(value);
            EventBus.getDefault().post(updater);
            lastSentTime = System.currentTimeMillis();
        }
    }
}

onEventMainThread方法:

public void onEventMainThread(ReadingsUpdateData data){
    mSeries1.appendData(new DataPoint(counter, data), true, 100);
    counter++;
}
于 2015-05-25T05:53:45.747 回答
1

您的 PlotsRun 实际上太快了:一旦它完成执行,它就会通过调用mHandler.post(processPlots);.

首先,您需要使数据缓冲区独立于数据收集器和数据可视化器:创建一个可以接收(从收集器)和传递(到可视化器)数据的对象。因此,每个组件都可以完全独立地工作。而且您的数据对象独立于任何线程。您的数据收集器可以在需要时将数据推送到您的数据对象,并且您的主线程可以根据常规计时器查询您的数据对象。

然后,在这个缓冲区上加一个锁,这样需要访问数据缓冲区的其他 2 个对象都不能同时进行(这将导致崩溃)。这个锁可以是一个简单synchronized的方法声明。

这应该确保您的应用程序不会因为并发访问而崩溃(我认为这应该是您的主要问题)。

然后,如果新数据到达时主数据集合已经在使用中,您可以通过创建额外的缓冲区来存储临时数据来开始优化数据对象,或者制作实际数据的副本以使其始终可供主线程使用,即使是新数据当主线程查询值时,当前正在添加数据。

于 2015-05-25T07:05:17.700 回答
0

我会设置这样的东西:

public class MainActivity extends Activity {

 private class ProcessThread extends Thread{
        private float value = 0f;
        private ReadingsUpdateData updater = new ReadingsUpdateData(values);
        public void run() {
            while(true) {
                value = getRandom();
                updater.setData(value);
                EventBus.getDefault().post(updater);
            }
        }
    }    

    @Override
    protected void onResume() {
        super.onResume();
        testThread.start();
    }

}



public class GraphFragment extends Fragment {

  private Handler mHandler;
  private Queue<ReadingsUpdateData> queue;

  @Override
  public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);
    mHandler = new Handler(Looper.getMainLooper());
  }

  public void onEvent(ReadingsUpdateData data){
    synchronized(queue){
        queue.add(data);
    }

    if (mHandler != null) {
       mHandler.post(processPlots);
    }
  }

  //implement pause/resume to register/unregister from event bus


 private Runnable processPlots = new Runnable {

        @Override
        public void run() {
            synchronized(queue) {
              if (queue.size()>0) {
                mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100);
                counter++;
              }
            }
        }
    }          

}
于 2015-05-22T21:09:55.190 回答
0

尝试使用可以从 Fragment 或 Activity 执行的 AsyncTask。这是AsyncTask的 Android 文档的链接

public class SomeAsyncTask extends AsyncTask<Object,Void, Object>{
        @Override
        protected void onPreExecute(){

        }
        @Override
        protected Object doInBackground(Object… params) {
        //make your request for any data here
           return  getData();


        }
        @Override
        protected void onPostExecute(Object object){
        //update your UI elements here
        mSeries1. appendData(object);           
        }
    }
于 2015-05-25T15:22:53.037 回答