3

アプリケーションのマルチスレッドを理解するのに大きな問題があり、そのためにバグが見つかりました。私はすべての可能性を考えていることを確認しましたが、それでもさまざまな (時には予期しない) エラーが発生しています。

たぶん、ここにいる誰かが私にアドバイスをくれるでしょう、私が何をすべきか。

私のプロジェクトでは、2 つの外部ライブラリを使用しています。

  • GraphView - グラフ描画用のビューを提供します
  • EventBus - アプリ コンポーネント間の簡単な通信のためのインターフェイスを提供します

アプリに関しては、次のような構造になっています。

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

アイデアは、ProcessThreadデータを計算し、一定の値のストリームをGraphFragmentthroughtに提供することですEventBus。で必要なものがありGraphFragmentます。SeriesGraphView

に従ってリアルタイムでグラフを更新するには、新しいグラフを作成する必要があるため、次のRunnableように作成しました。

private class PlotsRun implements Runnable{

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

フラグメントのメソッドから開始すると、onResume()すべてが魅力的に機能します。

残念ながら、私が言及したように、私は別のスレッドからの外部データを使用しています。それを取得するには、(ドキュメントGraphFragmentによると)メソッドを使用しています。onEventMainThread()

そして、ここで何をしようとも、データを渡してPlotsRunオブジェクトのグラフを更新することはできません。これまでのところ、私は試しました:

  • using Queue- add value inonEventMainThreadと get in PlotsRun。runnable は、メソッドがキューを更新できるよりも高速に読み取りを行うことが判明しました。
  • さまざまなバッファの作成 - 結果は とまったく同じQueueです。
  • mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);から直接呼び出すonEventMainThread- ある時点でフリーズします。
  • onEvent()ランナブル内にメソッドを作成し、そこから呼び出します-UIをmHandler.post()ブロックしていて、更新がスナップショットのように見えます。
  • synchronized()ブロックの有無にかかわらず、言及されているすべてを使用します。

私が理解するのが非常に難しいのは、(ある時点で)正しく機能しているこのランナブルです。

公式の Android ブログで述べられているように、非 UI スレッドから UI を更新することはできません。これが、内部で別のスレッドを使用できない理由ですGraphFragment。しかし、ランナブルをチェックすると、メインスレッド (UI) で実行されています。これが、無限を作成できず、while loop代わりに呼び出す必要がある理由mHandler.post(this)です。

それでも、onEventMainThreadメソッドよりも高速である (より頻繁に呼び出される) ため、別のスレッドのように動作します。

からのデータを使用してグラフ (またはどこを見るべきか) を更新できるようにするにはどうすればよいProcessThreadですか?

EDIT1:

@Matt Wolfe リクエストに答えると、この問題のコードの最も重要な部分であると思われるものを含め、必要なすべての変数がどのように宣言されているかを示しています。これは非常に単純化された例です:

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);
        }
    }

この高速読み取りの問題のため、保護のために実行可能な if が追加されています。しかし、常に何かがあるはずなので、ここにあるべきではありません (少なくとも私はそれを期待しています)。

追加するもう1つのこと-単純Log.dでカウントする変数を内部onEventMainThreadに配置すると、その値が正しく更新および表示されましたが、残念ながらlogcatはメインUIではありません.

EDIT2:

これは主に @MattWolfeコメントへの応答です

mHandler は、GrapgFragment で宣言および作成された単なる変数です。

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

はい、そうです、私はmHandler.post()遅滞なく使用しています。多少の遅延を使用して、違いがあるかどうかを確認します。

前に触れていなかったのは、ProcessThreadが他のフラグメントにもデータを提供しているということです。互いに干渉したり、リソースを共有したりしないことを心配しないでください。これが私が使用している理由ですEventBus

EDIT3:

GraphFragmentこれは、別のスレッドとrunOnMainThreadメソッドを使用して別のアイデアとして使用したコードです。

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);
        }
    }

残念ながら、どちらも機能していません。

4

4 に答える 4

3

初めに、

次の例の実行可能な部分は、リアルタイムのデータ更新をアニメーション化するためのものですappendData()。新しい実行可能な部分を作成せずに呼び出すことを選択できます。appendData()ただし、メインスレッドから呼び出す必要があります。

第二に、

appendData()関数から直接関数を呼び出すことができますがonEventMainThread、このアプローチでは UI がハングすることがあると指摘したように、この動作の考えられる理由の 1 つは、おそらくイベントを頻繁に投稿しすぎていることです。UI を頻繁に更新すると、最終的に UI がハングします。点。これを回避するには、次のことを実行できます。

UI を頻繁に更新すると、UI がハングする可能性もあります。解決策は次のとおりです。

最後に送信されたイベント時間を保存し、新しいイベントを送信する前に比較するロジックをいくつか挿入しProcessThread、差が 1 秒未満の場合は、後で送信するために保存し、次の計算が完了したときに時間を再度比較します。最新の計算はグラフの最新の状態を表すことができるため、イベントを配列で送信するか、最新のイベントのみを送信するよりも、1 秒よりも短くなりますか?

それが役立つことを願っています!

編集:(コメント1と2に応じて)

あなたが試したことが、更新されたコードを投稿することでより良いアイデアが得られるかどうかはわかりません。onEventMainThreadしかし、ランナブルまたはランナブルにタイムチェック機能を実装しようとしたと思いますがPlotsRun、それは正しいですか? はいの場合、それはあなたにとってあまり役に立たないのではないかと思います. 代わりに、ProcessThread 内でこの時間をチェックするチェックを実装し、しきい値時間に達した場合にのみ新しいイベントを投稿する必要があります。次の理由により:

1- バックエンドの EventBus が自動的に新しいランナブルを作成し、その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

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 に答える
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 に答える