Valore restituito da Thread


93

Ho un metodo con a HandlerThread. Un valore viene modificato all'interno di Threade vorrei restituirlo al test()metodo. C'è un modo per fare questo?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}

Se il thread principale deve attendere che il thread del gestore termini prima di tornare dal metodo, perché utilizzare un thread del gestore in primo luogo?
JB Nizet

1
@JBNizet Non ho incluso la complessità di ciò che fa effettivamente il Thread. Riceve le coordinate GPS quindi sì, ho bisogno di un thread.
Neeta

2
Indipendentemente dalla complessità del thread, se il thread che lo avvia attende immediatamente il suo risultato dopo averlo avviato, non ha senso avviare un thread diverso: il thread di partenza verrà bloccato come se facesse il lavoro da solo.
JB Nizet

@JBNizet Non sono molto sicuro di cosa intendi .. ti dispiacerebbe spiegarlo in un modo diverso?
Neeta

1
Un thread viene utilizzato per poter eseguire qualcosa in background ed essere in grado di fare qualcos'altro mentre il thread in background viene eseguito. Se si avvia un thread e quindi si blocca immediatamente fino a quando il thread non si ferma, è possibile eseguire da soli l'attività svolta dal thread e non farebbe alcuna differenza, tranne che sarebbe molto più semplice.
JB Nizet

Risposte:


75

È possibile utilizzare un array di variabili finali locali. La variabile deve essere di tipo non primitivo, quindi puoi usare un array. È inoltre necessario sincronizzare i due thread, ad esempio utilizzando un 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.
}

Puoi anche usare un Executore un Callablecome questo:

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

3
Ehm ... no. Questo codice non è corretto. L'accesso al valore non è sincronizzato correttamente.
G. Blake Meike

5
In realtà non abbiamo bisogno di una sincronizzazione esplicita per gli accessi al valore a causa delle garanzie di consistenza della memoria di CountDownLatch. La creazione della matrice di valori avviene prima dell'avvio di uiThread (regola dell'ordine del programma) che si sincronizza con l'assegnazione di 2 al valore [0] ( inizio del thread ) che avviene prima di latch.countDown () (regola dell'ordine del programma) che avviene prima del latch .await () (garanzia di CountDownLatch) che avviene prima della lettura dal valore [0] (regola dell'ordine del programma).
Adam Zalcman

Sembra che tu abbia ragione riguardo a Latch! ... in tal caso, la sincronizzazione del metodo di esecuzione è inutile.
G. Blake Meike

Buon punto. Devo aver copiato e incollato dal codice di OP. Corretto.
Adam Zalcman


89

Di solito lo faresti in questo modo

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Quindi puoi creare il thread e recuperare il valore (dato che il valore è stato impostato)

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

tl;drun thread non può restituire un valore (almeno non senza un meccanismo di callback). Dovresti fare riferimento a un thread come una normale classe e chiedere il valore.


2
Funziona davvero? Capisco The method getValue() is undefined for the type Thread.
pmichna

1
@pmichna, buon avvistamento. Cambiando da t.getValue()a foo.getValue().
Johan Sjöberg

3
Sì! Molto bene! ftw "volatile"! A differenza della risposta accettata, questa è corretta!
G. Blake Meike

11
@HamzahMalik Per assicurarti che il thread finisca, usa Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. I t.join()blocchi fino al termine del thread.
Daniel

2
@HariKiran sì corretto, ogni thread restituisce un valore indipendente.
rootExplorr

28

Quello che stai cercando è probabilmente l' Callable<V>interfaccia al posto di Runnablee il recupero del valore con un Future<V>oggetto, che ti consente anche di attendere fino a quando il valore non è stato calcolato. Puoi ottenere ciò con un ExecutorService, da cui puoi ottenere 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();
}

8

Che ne dici di questa soluzione?

Non usa la classe Thread, ma È concorrente e in un certo senso fa esattamente ciò che richiedi

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

Ora tutto ciò che fai è dire che value.get()ogni volta che devi prendere il valore restituito, il thread viene avviato proprio nel momento in cui dai valueun valore in modo da non doverlo mai dire threadName.start()su di esso.

Cos'è Future, è una promessa al programma, prometti al programma che otterrai il valore di cui ha bisogno nel prossimo futuro

Se lo chiami .get()prima che sia finito, il thread che lo sta chiamando aspetterà semplicemente finché non sarà finito


Ho separato il codice fornito in due cose, una è l'avvio del pool che faccio nella classe Application (sto parlando di Android) e in secondo luogo utilizzo il pool nei luoghi in cui ne ho bisogno ... Anche riguardo all'uso di Executors.newFixedThreadPool ( 2) ho usato Executors.newSingleThreadExecutor () .. poiché avevo bisogno di una sola attività in esecuzione alla volta per le chiamate al server ... La tua risposta è perfetta @electirc coffee thanks
Gaurav Pangam

@Electric come fai a sapere quando ottenere il valore? Credo che il poster originale volesse restituire questo valore nel suo metodo. L'uso della chiamata .get () recupererà il valore, ma solo se l'operazione è completata. Non lo saprebbe con una chiamata cieca .get ()
portfoliobuilder

4

Se si desidera il valore dal metodo chiamante, è necessario attendere che il thread finisca, il che rende l'utilizzo dei thread un po 'inutile.

Per rispondere direttamente alla tua domanda, il valore può essere memorizzato in qualsiasi oggetto modificabile sia il metodo chiamante che il thread hanno entrambi un riferimento. Potresti usare l'esterno this, ma non sarà particolarmente utile se non per esempi banali.

Una piccola nota sul codice nella domanda: l'estensione Threadè di solito uno stile scadente. In effetti, estendere le classi inutilmente è una cattiva idea. Ho notato che il tuo runmetodo è sincronizzato per qualche motivo. Ora, poiché l'oggetto in questo caso è il, Threadpuoi interferire con qualsiasi cosa Threadusi il suo blocco per (nell'implementazione di riferimento, qualcosa a che fare con joinIIRC).


"allora dovrebbe aspettare che il thread finisca, il che rende l'uso dei thread un po 'inutile" ottimo punto!
likejudo

4
Di solito è inutile, ma in Android non è possibile effettuare una richiesta di rete a un server sul thread principale (per mantenere l'app reattiva), quindi dovrai utilizzare un thread di rete. Esistono scenari in cui è necessario il risultato prima che l'app possa riprendere.
Udi Idan

2

Da Java 8 in poi abbiamo CompletableFuture. Nel tuo caso, puoi utilizzare il metodo supplyAsync per ottenere il risultato dopo l'esecuzione.

Si prega di trovare alcuni rif. qui https://www.baeldung.com/java-completablefuture#Processing

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

   completableFuture.get() //gives you the value

1

L'uso di Future descritto nelle risposte precedenti fa il lavoro, ma in modo un po 'meno significativo poiché f.get (), blocca il thread fino a quando non ottiene il risultato, il che viola la concorrenza.

La soluzione migliore è utilizzare ListenableFuture di Guava. Un esempio :

    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)
        {

        }
    };

1

Con piccole modifiche al codice, puoi ottenerlo in un modo più generico.

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

Soluzione:

  1. Crea un Handlerthread in UI, chiamato comeresponseHandler
  2. Inizializza questo Handlerda Looperdel thread dell'interfaccia utente.
  3. In HandlerThread, posta un messaggio su questoresponseHandler
  4. handleMessgaemostra un Toastvalore ricevuto dal messaggio. Questo oggetto Message è generico ed è possibile inviare diversi tipi di attributi.

Con questo approccio, puoi inviare più valori al thread dell'interfaccia utente in momenti diversi. È possibile eseguire (postare) molti Runnableoggetti su questo HandlerThreade ciascuno Runnablepuò impostare il valore Messagenell'oggetto, che può essere ricevuto dal thread dell'interfaccia utente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.