Как создать поток Looper, а затем отправить ему сообщение сразу?

У меня есть рабочий поток, который сидит в фоновом режиме, обрабатывая сообщения. Что-то вроде этого:

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

Из основного потока (поток пользовательского интерфейса, не важно) Я хотел бы сделать что-то вроде этого:

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

Проблема заключается в том, что это меня подводит к прекрасному состоянию гонки: во время чтения worker.handler нет способа убедиться, что рабочий поток уже назначен этому полю!

Я не могу просто создать Handler из конструктора Worker, потому что конструктор работает в основном потоке, поэтому Handler свяжется с неправильным потоком.

Это вряд ли кажется необычным сценарием. Я могу придумать несколько обходных решений, все они уродливые:

  • Что-то вроде этого:

    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(), мы никогда не будем разбужены!

  • Передача начального конструктора Message в конструктор Worker, содержащий метод run(). Специальное решение не будет работать для нескольких сообщений, или если мы не хотим отправлять его сразу же, но вскоре.

  • Ожидание в ожидании до тех пор, пока поле Handler больше не будет null. Да, последнее средство...

Я хотел бы создать Handler и MessageQueue от имени потока Worker, но это не представляется возможным. Каков самый изящный выход из этого?

Ответ 1

Возможное решение (минус проверка ошибок), благодаря 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;
}

Ключевое различие заключается в том, что он не проверяет, работает ли рабочий поток, но что он фактически создал петлитель; и способ сделать это - сохранить петлитель в частном поле. Ницца!

Ответ 2

взгляните на исходный код HandlerThread

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

В принципе, если вы расширяете Thread в рабочем процессе и реализуете свой собственный Looper, то ваш основной класс потока должен расширять рабочий и устанавливать там свой обработчик.

Ответ 3

Это мои решения: 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();
    }
}

Ответ 4

    class WorkerThread extends Thread {
            private Exchanger<Void> mStartExchanger = new Exchanger<Void>();
            private Handler mHandler;
            public Handler getHandler() {
                    return mHandler;
            }
            @Override
            public void run() {
                    Looper.prepare();
                    mHandler = new Handler();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
                    Looper.loop();
            }

            @Override
            public synchronized void start() {
                    super.start();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
            }
    }