77

メッセージを処理するバックグラウンドにあるワーカースレッドがあります。このようなもの:

class Worker extends Thread {

    public volatile Handler handler; // actually private, of course

    public void run() {
        Looper.prepare();
        mHandler = new Handler() { // the Handler hooks up to the current Thread
            public boolean handleMessage(Message msg) {
                // ...
            }
        };
        Looper.loop();
    }
}

メインスレッド(UIスレッド、それは重要ではありません)から私は次のようなことをしたいと思います:

Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);

問題は、これが私を美しい競合状態に設定することworker.handlerです。読み取り時に、ワーカースレッドがすでにこのフィールドに割り当てられていることを確認する方法はありません。

コンストラクターはメインスレッドで実行されるため、コンストラクターHandlerからを単純に作成することはできません。そのため、コンストラクターは間違ったスレッドに関連付けられます。WorkerHandler

これは珍しいシナリオのようには思えません。私はいくつかの回避策を思い付くことができますが、それらはすべて醜いです:

  1. このようなもの:

    class Worker extends Thread {
    
        public volatile Handler handler; // actually private, of course
    
        public void run() {
            Looper.prepare();
            mHandler = new Handler() { // the Handler hooks up to the current Thread
                public boolean handleMessage(Message msg) {
                    // ...
                }
            };
            notifyAll(); // <- ADDED
            Looper.loop();
        }
    }
    

    そしてメインスレッドから:

    Worker worker = new Worker();
    worker.start();
    worker.wait(); // <- ADDED
    worker.handler.sendMessage(...);
    

    しかし、これも信頼できるものではありません。のnotifyAll()前に発生した場合wait()、私たちは決して起こされません!

  2. イニシャルMessageWorker'sコンストラクターに渡し、run()メソッドにポストさせます。アドホックソリューションは、複数のメッセージに対しては機能しません。または、すぐに送信したくない場合はすぐに送信します。

  3. handlerビジー-フィールドがなくなるまで待機しますnull。うん、最後の手段...

スレッドに代わってHandlerとを作成したいのですが、これは不可能のようです。これから最もエレガントな方法は何ですか?MessageQueueWorker

4

4 に答える 4

64

CommonsWareのおかげで、最終的な解決策(エラーチェックを除く):

class Worker extends HandlerThread {

    // ...

    public synchronized void waitUntilReady() {
        d_handler = new Handler(getLooper(), d_messageHandler);
    }

}

そしてメインスレッドから:

Worker worker = new Worker();
worker.start();
worker.waitUntilReady(); // <- ADDED
worker.handler.sendMessage(...);

HandlerThread.getLooper()これは、ルーパーが初期化されるまでのどのブロックのセマンティクスのおかげで機能します。


ちなみに、これは上記の私の解決策 #1 に似ていHandlerThreadます。

public void run() {
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Looper.loop();
}

public Looper getLooper() {
    synchronized (this) {
        while (mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

主な違いは、ワーカー スレッドが実行されているかどうかをチェックするのではなく、実際にルーパーを作成したことです。その方法は、ルーパーをプライベート フィールドに格納することです。良い!

于 2011-01-31T20:35:21.283 に答える
1

のソースコードを見てくださいHandlerThread

@Override
     public void run() {
         mTid = Process.myTid();
         Looper.prepare();
         synchronized (this) {
             mLooper = Looper.myLooper();
             notifyAll();
         }
         Process.setThreadPriority(mPriority);
         onLooperPrepared();
         Looper.loop();
         mTid = -1;
     }

基本的に、Worker で Thread を拡張し、独自の Looper を実装する場合、メイン スレッド クラスは Worker を拡張し、そこにハンドラーを設定する必要があります。

于 2015-08-15T22:58:28.833 に答える
1

これは私のソリューションです: MainActivity:

//Other Code

 mCountDownLatch = new CountDownLatch(1);
        mainApp = this;
        WorkerThread workerThread = new WorkerThread(mCountDownLatch);
        workerThread.start();
        try {
            mCountDownLatch.await();
            Log.i("MsgToWorkerThread", "Worker Thread is up and running. We can send message to it now...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Toast.makeText(this, "Trial run...", Toast.LENGTH_LONG).show();
        Message msg = workerThread.workerThreadHandler.obtainMessage();
        workerThread.workerThreadHandler.sendMessage(msg);

WorkerThread クラス:

public class WorkerThread extends Thread{

    public Handler workerThreadHandler;
    CountDownLatch mLatch;

    public WorkerThread(CountDownLatch latch){

        mLatch = latch;
    }


    public void run() {
        Looper.prepare();
        workerThreadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {

                Log.i("MsgToWorkerThread", "Message received from UI thread...");
                        MainActivity.getMainApp().runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.getMainApp().getApplicationContext(), "Message received in worker thread from UI thread", Toast.LENGTH_LONG).show();
                                //Log.i("MsgToWorkerThread", "Message received from UI thread...");
                            }
                        });

            }

        };
        Log.i("MsgToWorkerThread", "Worker thread ready...");
        mLatch.countDown();
        Looper.loop();
    }
}
于 2015-11-24T13:59:14.730 に答える