Tengo un hilo de trabajo que se encuentra en segundo plano, procesando mensajes. Algo como esto:
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();
}
}
Desde el hilo principal (hilo de la interfaz de usuario, no es que importe) me gustaría hacer algo como esto:
Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);
El problema es que esto me prepara para una hermosa condición de carrera: en el momento en que worker.handler
se lee, ¡no hay forma de estar seguro de que el hilo de trabajo ya se haya asignado a este campo!
No puedo simplemente crear el Handler
desde el Worker
constructor de, porque el constructor se ejecuta en el hilo principal, por lo que Handler
se asociará con el hilo incorrecto.
Esto no parece un escenario poco común. Puedo encontrar varias soluciones, todas feas:
Algo como esto:
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(); } }
Y del hilo principal:
Worker worker = new Worker(); worker.start(); worker.wait(); // <- ADDED worker.handler.sendMessage(...);
Pero esto tampoco es confiable: si
notifyAll()
sucede antes delwait()
, ¡nunca nos despertaremos!Pasando una inicial
Message
alWorker
constructor de, haciendo que elrun()
método la publique. Una solución ad-hoc, no funcionará para varios mensajes, o si no queremos enviarla de inmediato, pero poco después.Ocupado esperando hasta que el
handler
campo ya no esténull
. Sí, un último recurso ...
Me gustaría crear un Handler
y MessageQueue
en nombre del Worker
hilo, pero esto no parece ser posible. ¿Cuál es la forma más elegante de salir de esto?
fuente
HandlerThread
?getLooper()
método se bloquea hasta que tenemos unLooper
, luego podemos usarnew Handler(worker.getLooper())
desde el hilo principal para inicializar elHandler
. Eso resolvería el problema, ¿verdad?HandlerThread
encaja en suWorker
patrón. Al menos, lo explicará mejor de lo que yo podría, ya que era su problema y su implementación de una solución; acabo de señalar una clase de ayuda para abordar el problema.Respuestas:
Solución eventual (menos verificación de errores), gracias a CommonsWare:
class Worker extends HandlerThread { // ... public synchronized void waitUntilReady() { d_handler = new Handler(getLooper(), d_messageHandler); } }
Y del hilo principal:
Worker worker = new Worker(); worker.start(); worker.waitUntilReady(); // <- ADDED worker.handler.sendMessage(...);
Esto funciona gracias a la semántica de
HandlerThread.getLooper()
qué bloques hasta que se inicializa el looper.Por cierto, esto es similar a mi solución n. ° 1 anterior, ya que
HandlerThread
se implementa aproximadamente de la siguiente manera (me encanta el código abierto):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; }
La diferencia clave es que no verifica si el hilo de trabajo se está ejecutando, sino que en realidad ha creado un bucleador; y la forma de hacerlo es almacenar el looper en un campo privado. ¡Agradable!
fuente
d_handler
?d_messageHandler
maneja los mensajes, ¿verdad? Pero estás enviando mensajes usandowork.handler
echa un vistazo al código fuente de
HandlerThread
@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
Básicamente, si está extendiendo Thread en worker e implementando su propio Looper, entonces su clase de thread principal debería extender worker y establecer su controlador allí.
fuente
Estas son mis soluciones: 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);
La clase 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(); } }
fuente
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(); } } }
fuente