Rückgabewert vom Thread

92

Ich habe eine Methode mit a HandlerThread. Ein Wert wird innerhalb von geändert Threadund ich möchte ihn an die test()Methode zurückgeben. Gibt es eine Möglichkeit, dies zu tun?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
quelle
Wenn der Hauptthread warten muss, bis der Handler-Thread beendet ist, bevor er von der Methode zurückkehrt, warum sollte er überhaupt einen Handler-Thread verwenden?
JB Nizet
1
@JBNizet Ich habe die Komplexität dessen, was der Thread tatsächlich tut, nicht berücksichtigt. Es werden GPS-Koordinaten angezeigt, also brauche ich einen Thread.
Neeta
2
Unabhängig von der Komplexität des Threads macht es keinen Sinn, einen anderen Thread zu starten, wenn der Thread, der ihn startet, sofort nach dem Start auf sein Ergebnis wartet: Der Start-Thread wird blockiert, als ob er die Arbeit selbst erledigt hätte.
JB Nizet
@JBNizet Ich bin mir nicht sicher, was du meinst. Würde es dir etwas ausmachen, es anders zu erklären?
Neeta
1
Ein Thread wird verwendet, um etwas im Hintergrund ausführen zu können und um etwas anderes zu tun, während der Hintergrund-Thread ausgeführt wird. Wenn Sie einen Thread starten und dann sofort blockieren, bis der Thread stoppt, können Sie die vom Thread erledigte Aufgabe selbst ausführen, und es würde keinen Unterschied machen, außer es wäre viel einfacher.
JB Nizet

Antworten:

75

Sie können ein lokales endgültiges Variablenarray verwenden. Die Variable muss nicht primitiv sein, damit Sie ein Array verwenden können. Sie müssen auch die beiden Threads synchronisieren, z. B. mithilfe eines CountDownLatch :

public void test()
{   
    final CountDownLatch latch = new CountDownLatch(1);
    final int[] value = new int[1];
    Thread uiThread = new HandlerThread("UIHandler"){
        @Override
        public void run(){
            value[0] = 2;
            latch.countDown(); // Release await() in the test thread.
        }
    };
    uiThread.start();
    latch.await(); // Wait for countDown() in the UI thread. Or could uiThread.join();
    // value[0] holds 2 at this point.
}

Sie können auch ein Executorund ein Callablesolches verwenden:

public void test() throws InterruptedException, ExecutionException
{   
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() {
            return 2;
        }
    };
    Future<Integer> future = executor.submit(callable);
    // future.get() returns 2 or raises an exception if the thread dies, so safer
    executor.shutdown();
}
Adam Zalcman
quelle
3
Ähm ... nein. Dieser Code ist nicht korrekt. Der Zugriff auf den Wert ist nicht korrekt synchronisiert.
G. Blake Meike
5
Aufgrund der Speicherkonsistenzgarantien von CountDownLatch benötigen wir keine explizite Synchronisierung für die Wertzugriffe. Die Wertarray-Erstellung erfolgt vor dem Start von uiThread (Programmreihenfolge-Regel), die synchronisiert wird, mit der Zuweisung von 2 zu Wert [0] ( Thread-Start ), die vor dem Latch erfolgt. CountDown () (Programmreihenfolge-Regel), die vor dem Latch erfolgt .await () (Garantie von CountDownLatch), die vor dem Ablesen von Wert [0] (Programmreihenfolge-Regel) erfolgt.
Adam Zalcman
Sieht so aus, als ob Sie mit dem Riegel Recht haben! ... in diesem Fall ist die Synchronisation der Ausführungsmethode nutzlos.
G. Blake Meike
Guter Punkt. Ich muss aus dem OP-Code kopiert haben. Korrigiert.
Adam Zalcman
Hier ist ein Beispiel für CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Seagull
85

Normalerweise würden Sie es so etwas tun

 public class Foo implements Runnable {
     private volatile int value;

     @Override
     public void run() {
        value = 2;
     }

     public int getValue() {
         return value;
     }
 }

Dann können Sie den Thread erstellen und den Wert abrufen (vorausgesetzt, der Wert wurde festgelegt).

Foo foo = new Foo();
Thread thread = new Thread(foo);
thread.start();
thread.join();
int value = foo.getValue();

tl;drEin Thread kann keinen Wert zurückgeben (zumindest nicht ohne Rückrufmechanismus). Sie sollten einen Thread wie eine normale Klasse referenzieren und nach dem Wert fragen.

Johan Sjöberg
quelle
2
Funktioniert das wirklich? Ich verstehe The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, gute Beobachtung. Wechsel von t.getValue()zu foo.getValue().
Johan Sjöberg
3
Ja! Gut gemacht! "flüchtig" ftw! Im Gegensatz zur akzeptierten Antwort ist diese richtig!
G. Blake Meike
11
@HamzahMalik Um sicherzustellen, dass der Thread fertig ist, verwenden Sie Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Die t.join()Blöcke bis der Thread fertig ist.
Daniel
2
@ HariKiran ja richtig, jeder Thread gibt einen unabhängigen Wert zurück.
rootExplorr
28

Was Sie suchen, ist wahrscheinlich die Callable<V>Schnittstelle anstelle von Runnableund das Abrufen des Werts mit einem Future<V>Objekt, wodurch Sie auch warten können, bis der Wert berechnet wurde. Sie können dies mit einem erreichen ExecutorService, von dem Sie erhalten können Executors.newSingleThreadExecutor().

public void test() {
    int x;
    ExecutorService es = Executors.newSingleThreadExecutor();
    Future<Integer> result = es.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            // the other thread
            return 2;
        }
    });
    try {
        x = result.get();
    } catch (Exception e) {
        // failed
    }
    es.shutdown();
}
Detheroc
quelle
8

Wie wäre es mit dieser Lösung?

Es verwendet nicht die Thread-Klasse, aber es ist gleichzeitig und in gewisser Weise genau das, was Sie anfordern

ExecutorService pool = Executors.newFixedThreadPool(2); // creates a pool of threads for the Future to draw from

Future<Integer> value = pool.submit(new Callable<Integer>() {
    @Override
    public Integer call() {return 2;}
});

Jetzt müssen Sie nur noch sagen, value.get()wann immer Sie Ihren zurückgegebenen Wert abrufen müssen. Der Thread wird in der Sekunde gestartet, in der Sie valueeinen Wert angeben, damit Sie nie etwas dazu sagen müssen threadName.start().

Was Futureist, ist ein Versprechen an das Programm, Sie versprechen dem Programm, dass Sie ihm den Wert geben, den es irgendwann in naher Zukunft benötigt

Wenn Sie es aufrufen .get(), bevor es fertig ist, wartet der Thread, der es aufruft, einfach, bis es fertig ist

Elektrischer Kaffee
quelle
Ich habe den bereitgestellten Code in zwei Dinge unterteilt: Die Poolinitiierung, die ich in der Anwendungsklasse mache (ich spreche von Android), und zweitens verwende ich den Pool an den Stellen, an denen ich ihn benötige ... Auch in Bezug auf die Verwendung von Executors.newFixedThreadPool ( 2) Ich habe Executors.newSingleThreadExecutor () .. verwendet, da ich jeweils nur eine Aufgabe für Serveranrufe benötigte ... Ihre Antwort ist perfekt @electirc Kaffee danke
Gaurav Pangam
@Electric Woher weißt du, wann du den Wert bekommst? Ich glaube, das Originalplakat wollte diesen Wert in seiner Methode zurückgeben. Wenn Sie den Aufruf .get () verwenden, wird der Wert abgerufen, jedoch nur, wenn der Vorgang abgeschlossen ist. Er würde es nicht mit einem blinden .get () -Aufruf wissen
portfoliobuilder
4

Wenn Sie den Wert von der aufrufenden Methode erhalten möchten, sollte er warten, bis der Thread beendet ist, was die Verwendung von Threads etwas sinnlos macht.

Um Ihre Frage direkt zu beantworten, kann der Wert in jedem veränderlichen Objekt gespeichert werden, auf das sowohl die aufrufende Methode als auch der Thread verweisen. Sie könnten das Äußere verwenden this, aber das wird nur für triviale Beispiele besonders nützlich sein.

Ein kleiner Hinweis zum Code in der Frage: Das Erweitern Threadist normalerweise ein schlechter Stil. In der Tat ist es unnötig, Klassen unnötig zu erweitern. Ich stelle fest, dass Ihre runMethode aus irgendeinem Grund synchronisiert ist. Da es sich in diesem Fall um das Objekt handelt Thread, können Sie in das eingreifen , wofür Threaddie Sperre verwendet wird (in der Referenzimplementierung etwas mit joinIIRC zu tun ).

Tom Hawtin - Tackline
quelle
"Dann sollte es warten, bis der Thread fertig ist, was die Verwendung von Threads etwas sinnlos macht."
Likejudo
4
Es ist normalerweise sinnlos, aber in Android können Sie keine Netzwerkanforderung an einen Server im Haupt-Thread stellen (damit die App reagiert), sodass Sie einen Netzwerk-Thread verwenden müssen. Es gibt Szenarien, in denen Sie das Ergebnis benötigen, bevor die App fortgesetzt werden kann.
Udi Idan
2

Ab Java 8 haben wir CompletableFuture. In Ihrem Fall können Sie die Methode supplyAsync verwenden , um das Ergebnis nach der Ausführung abzurufen .

Bitte finden Sie einige ref. hier https://www.baeldung.com/java-completablefuture#Processing

    CompletableFuture<Integer> completableFuture
      = CompletableFuture.supplyAsync(() -> yourMethod());

   completableFuture.get() //gives you the value
Vinto
quelle
1

Die Verwendung von Future, wie in den obigen Antworten beschrieben, erledigt den Job, blockiert den Thread jedoch etwas weniger als f.get (), bis er das Ergebnis erhält, das die Parallelität verletzt.

Die beste Lösung ist die Verwendung von ListenableFuture von Guava. Ein Beispiel :

    ListenableFuture<Void> future = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1, new NamedThreadFactory).submit(new Callable<Void>()
    {
        @Override
        public Void call() throws Exception
        {
            someBackgroundTask();
        }
    });
    Futures.addCallback(future, new FutureCallback<Long>()
    {
        @Override
        public void onSuccess(Long result)
        {
            doSomething();
        }

        @Override
        public void onFailure(Throwable t)
        {

        }
    };
Bitsabhi
quelle
1

Mit kleinen Änderungen an Ihrem Code können Sie dies allgemeiner erreichen.

 final Handler responseHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                //txtView.setText((String) msg.obj);
                Toast.makeText(MainActivity.this,
                        "Result from UIHandlerThread:"+(int)msg.obj,
                        Toast.LENGTH_LONG)
                        .show();
            }
        };

        HandlerThread handlerThread = new HandlerThread("UIHandlerThread"){
            public void run(){
                Integer a = 2;
                Message msg = new Message();
                msg.obj = a;
                responseHandler.sendMessage(msg);
                System.out.println(a);
            }
        };
        handlerThread.start();

Lösung:

  1. Erstellen Sie einen HandlerIn-UI-Thread, der als bezeichnet wirdresponseHandler
  2. Initialisieren Sie diese Handleraus Looperdem UI - Thread.
  3. In HandlerThread, Post Nachricht auf diesemresponseHandler
  4. handleMessgaezeigt einen Toastmit dem von der Nachricht empfangenen Wert. Dieses Nachrichtenobjekt ist generisch und Sie können verschiedene Arten von Attributen senden.

Mit diesem Ansatz können Sie mehrere Werte zu unterschiedlichen Zeitpunkten an den UI-Thread senden. Sie können viele RunnableObjekte auf diesem Objekt ausführen (veröffentlichen) HandlerThreadund jedes Runnablekann einen Wert im MessageObjekt festlegen , der vom UI-Thread empfangen werden kann.

Ravindra Babu
quelle