web-dev-qa-db-fra.com

Comment créer un thread Looper, puis lui envoyer immédiatement un message?

J'ai un thread de travail qui se trouve en arrière-plan et traite les messages. Quelque chose comme ça:

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

À partir du thread principal (thread d'interface utilisateur, ce n'est pas important), je voudrais faire quelque chose comme ceci:

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

Le problème est que cela me met en place pour une belle condition de concurrence: au moment où worker.handler Est lu, il n'y a aucun moyen d'être sûr que le thread de travail a déjà été affecté à ce champ!

Je ne peux pas simplement créer le Handler à partir du constructeur de Worker, car le constructeur s'exécute sur le thread principal, donc le Handler s'associera au mauvais thread.

Cela ne semble guère être un scénario inhabituel. Je peux trouver plusieurs solutions de contournement, toutes laides:

  1. Quelque chose comme ça:

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

    Et à partir du fil principal:

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

    Mais ce n'est pas fiable non plus: si la notifyAll() se produit avant la wait(), alors nous ne serons jamais réveillés!

  2. Passer un Message initial au constructeur de Worker, avec la méthode run() le poster. Une solution ad-hoc, ne fonctionnera pas pour plusieurs messages, ou si nous ne voulons pas l'envoyer tout de suite mais peu de temps après.

  3. Attente occupée jusqu'à ce que le champ handler ne soit plus null. Oui, un dernier recours ...

Je voudrais créer un Handler et MessageQueue au nom du thread Worker, mais cela ne semble pas être possible. Quelle est la manière la plus élégante de s'en sortir?

73
Thomas

Solution éventuelle (vérification des erreurs moins), grâce à CommonsWare:

class Worker extends HandlerThread {

    // ...

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

}

Et à partir du fil principal:

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

Cela fonctionne grâce à la sémantique de HandlerThread.getLooper() qui bloque jusqu'à ce que le looper ait été initialisé.


Soit dit en passant, cela est similaire à ma solution n ° 1 ci-dessus, car le HandlerThread est implémenté à peu près comme suit (je dois aimer l'open source):

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 principale différence est qu'il ne vérifie pas si le thread de travail est en cours d'exécution, mais qu'il a effectivement créé un looper; et la façon de le faire est de stocker le boucleur dans un champ privé. Agréable!

60
Thomas

Voici mes solutions: 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 classe 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();
    }
}
1

jetez un oeil au code source 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;
     }

Fondamentalement, si vous étendez Thread in worker et implémentez votre propre Looper, votre classe de thread principale doit étendre worker et y définir votre gestionnaire.

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