1. ホーム
  2. android

Looperのスレッドを作成し、すぐにメッセージを送信するにはどうすればよいですか?

2023-09-16 02:14:05

質問

私は、バックグラウンドでメッセージを処理するワーカスレッドを持っています。このようなものです。

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 から Worker のコンストラクタはメインスレッドで実行されるからです。 Handler はそれ自体を間違ったスレッドに関連付けることになります。

これはほとんど珍しいシナリオではないようです。私はいくつかの回避策を思いつきますが、どれも醜いものばかりです。

  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 のコンストラクタに run() メソッドにポストさせます。アドホックな解決策ですが、複数のメッセージや、すぐに送信せずにすぐに送信したい場合にはうまくいきません。

  3. が送信されるまで忙しく待ちます。 handler フィールドが null . うん、最後の手段だ...。

を作成したいと思います。 HandlerMessageQueue を代表して Worker のスレッドに代わって、しかしこれは可能ではないようです。これを解決する最もエレガントな方法は何でしょうか?

どのように解決するのですか?

最終的な解決方法(エラーチェックを除く)は、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;
}

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