Wie erstelle ich einen Looper-Thread und sende ihm sofort eine Nachricht?

76

Ich habe einen Arbeitsthread im Hintergrund, der Nachrichten verarbeitet. Etwas wie das:

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

Vom Haupt-Thread (UI-Thread, nicht dass es darauf ankommt) möchte ich so etwas machen:

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

Das Problem ist, dass ich mich auf eine schöne Rennbedingung vorbereite: Zum Zeitpunkt des worker.handlerLesens kann nicht sichergestellt werden, dass der Worker-Thread diesem Feld bereits zugewiesen wurde!

Ich kann das nicht einfach Handleraus dem WorkerKonstruktor des erstellen , da der Konstruktor auf dem Hauptthread ausgeführt wird, sodass sich der Konstruktor Handlerdem falschen Thread zuordnet.

Dies scheint kaum ein ungewöhnliches Szenario zu sein. Ich kann mir mehrere Problemumgehungen einfallen lassen, die alle hässlich sind:

  1. Etwas wie das:

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

    Und aus dem Hauptthema:

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

    Aber das ist auch nicht zuverlässig: Wenn das notifyAll()vor dem passiert wait(), werden wir nie geweckt!

  2. Übergeben einer Initiale Messagean den WorkerKonstruktor des Konstruktors, damit die run()Methode sie veröffentlicht. Eine Ad-hoc-Lösung funktioniert nicht für mehrere Nachrichten oder wenn wir sie nicht sofort, sondern kurz danach senden möchten.

  3. Beschäftigt warten, bis das handlerFeld nicht mehr ist null. Ja, ein letzter Ausweg ...

Ich möchte ein Handlerund MessageQueueim Namen des WorkerThreads erstellen , aber dies scheint nicht möglich zu sein. Was ist der eleganteste Ausweg?

Thomas
quelle
12
Gibt es einen bestimmten Grund, den Sie nicht verwenden HandlerThread?
CommonsWare
2
@ CommonsWare: Hmm, war mir nicht bewusst, dass es existiert. Keine Querverweise in den Dokumenten. Seine getLooper()Methode blockiert, bis wir eine haben Looper, dann können wir new Handler(worker.getLooper()) aus dem Haupt-Thread die initialisieren Handler. Das würde das Problem lösen, oder?
Thomas
Ich glaube schon. OTOH, ich benutze es selbst nicht viel und daher fehlt mir möglicherweise etwas.
CommonsWare
3
@ CommonsWare: Es hat das Problem gelöst. Wenn Sie das als Antwort posten würden, werde ich ein großes grünes Häkchen daneben setzen;)
Thomas
9
Eigentlich denke ich, es wäre besser, wenn Sie es selbst beantworten würden, um zu erklären, wie HandlerThreadgut es in Ihr WorkerMuster passt . Zumindest erklären Sie es besser als ich, da es Ihr Problem und Ihre Implementierung einer Lösung war - ich habe nur auf eine Hilfsklasse hingewiesen, um das Problem anzugehen.
CommonsWare

Antworten:

64

Eventuelle Lösung (minus Fehlerprüfung) dank CommonsWare:

class Worker extends HandlerThread {

    // ...

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

}

Und aus dem Hauptthema:

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

Dies funktioniert dank der Semantik, HandlerThread.getLooper()die blockiert, bis der Looper initialisiert wurde.


Im Übrigen ähnelt dies meiner obigen Lösung Nr. 1, da die HandlerThreadungefähr wie folgt implementiert ist (ich muss Open Source lieben):

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

Der Hauptunterschied besteht darin, dass nicht überprüft wird, ob der Arbeitsthread ausgeführt wird, sondern dass tatsächlich ein Looper erstellt wurde. und der Weg, dies zu tun, besteht darin, den Greifer in einem privaten Feld zu lagern. Nett!

Thomas
quelle
1
Danke dafür. Ich möchte nur darauf hinweisen, dass waitUntilReady () nach worker.start () aufgerufen werden muss . Rückblickend klingt es ziemlich offensichtlich, aber ich habe ein bisschen gebraucht, um zu verstehen, was ich falsch gemacht habe, während ich eine Nullzeiger-Ausnahme bekam.
Fedepaol
4
@Snicolas können Sie nicht, da der Handler an den Thread gebunden ist, in dem er erstellt wurde. Das Initialisieren im HandlerThread-Konstruktor würde zu einem Handler im Hauptthread oder überall dort führen, wo HandlerThread erstellt wird. Wenn Sie den Handler-Konstruktor mit dem Looper des Handlers als Parameter verwenden möchten (der in waituntilready verwendet wird), bin ich mir ziemlich sicher, dass dies zu einem Deadlock führen würde.
Fedepaol
1
Ok, das ist ein guter Punkt. Danke fürs posten. Um den Handler vor Missbrauch zu schützen, können Sie die Handler-Methoden in den HandlerThread einbinden, der als Fassade fungieren kann, und jeden Aufruf an den Handler delegieren, nachdem überprüft wurde , ob der Handler initialisiert wurde. Dies wäre eine bessere Möglichkeit, es zu kapseln und zu verhindern, dass waitUntilReady vergessen wird.
Snicolas
2
@ Thomas was ist der Hauptzweck der d_handler? d_messageHandler behandelt die Nachrichten, richtig? Aber Sie senden Meesage mitwork.handler
Woyaru
1
Dies ist sehr interessant, aber es wäre nützlicher, wenn Sie einige Hinweise dazu geben würden, was d_handler und d_messageHandler sind und wie sie verwendet werden.
RenniePet
1

Schauen Sie sich den Quellcode von an HandlerThread

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

Wenn Sie Thread in Worker erweitern und Ihren eigenen Looper implementieren, sollte Ihre Haupt-Thread-Klasse Worker erweitern und Ihren Handler dort festlegen.

Beebee
quelle
1

Das sind meine Lösungen: 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);

Die WorkerThread-Klasse:

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();
    }
}
Somenath Mukhopadhyay
quelle
0
    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();
                    }
            }
    }
18446744073709551615
quelle