Valeur renvoyée par Thread

93

J'ai une méthode avec un HandlerThread. Une valeur est modifiée dans le Threadet j'aimerais la renvoyer à la test()méthode. Y a-t-il un moyen de faire cela?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
la source
Si le thread principal doit attendre la fin du thread de gestionnaire avant de revenir de la méthode, pourquoi utiliser un thread de gestionnaire en premier lieu?
JB Nizet
1
@JBNizet Je n'ai pas inclus la complexité de ce que fait réellement le Thread. Il obtient des coordonnées GPS, donc oui, j'ai besoin d'un fil.
Neeta
2
Quelle que soit la complexité du thread, si le thread qui le relance attend immédiatement son résultat après l'avoir démarré, il ne sert à rien de démarrer un thread différent: le thread de départ sera bloqué comme s'il faisait le travail lui-même.
JB Nizet
@JBNizet Je ne suis pas trop sûr de ce que vous voulez dire ... cela vous dérangerait-il de l'expliquer d'une manière différente?
Neeta
1
Un thread est utilisé pour être capable d'exécuter quelque chose en arrière-plan, et être capable de faire autre chose pendant que le thread d'arrière-plan s'exécute. Si vous démarrez un thread, puis bloquez immédiatement jusqu'à ce que le thread s'arrête, vous pouvez effectuer vous-même la tâche effectuée par le thread, et cela ne ferait aucune différence, sauf que ce serait beaucoup plus simple.
JB Nizet

Réponses:

75

Vous pouvez utiliser un tableau de variables final local. La variable doit être de type non primitif, vous pouvez donc utiliser un tableau. Vous devez également synchroniser les deux threads, par exemple à l'aide d'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.
}

Vous pouvez également utiliser un Executoret un Callablecomme ceci:

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
la source
3
Um non. Ce code n'est pas correct. L'accès à la valeur n'est pas correctement synchronisé.
G.Blake Meike
5
Nous n'avons pas réellement besoin de synchronisation explicite pour les accès à value en raison des garanties de cohérence de la mémoire de CountDownLatch. La création du tableau de valeurs se produit-avant le début de uiThread (règle d'ordre du programme) qui se synchronise-avec l'affectation de 2 à la valeur [0] ( début du thread ) qui se produit-avant latch.countDown () (règle d'ordre du programme) qui se produit-avant le verrou .await () (garantie de CountDownLatch) qui se produit-avant la lecture de la valeur [0] (règle d'ordre du programme).
Adam Zalcman
On dirait que vous avez raison sur le loquet! ... auquel cas, la synchronisation de la méthode run est inutile.
G. Blake Meike
Bon point. Je dois avoir copié-collé du code d'OP. Corrigée.
Adam Zalcman
Voici un exemple de CountDownLatch: developer.android.com/reference/java/util/concurrent/...
Seagull
89

Habituellement, vous feriez quelque chose comme ça

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Ensuite, vous pouvez créer le thread et récupérer la valeur (étant donné que la valeur a été définie)

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

tl;drun thread ne peut pas renvoyer une valeur (du moins pas sans mécanisme de rappel). Vous devez référencer un thread comme une classe ordinaire et demander la valeur.

Johan Sjöberg
la source
2
Cela fonctionne-t-il vraiment? Je reçois The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, bon repérage. Changement de t.getValue()à foo.getValue().
Johan Sjöberg
3
Oui! Bien joué! "volatile" ftw! Contrairement à la réponse acceptée, celle-ci est correcte!
G.Blake Meike
11
@HamzahMalik Pour vous assurer que le fil est terminé, utilisez Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Les t.join()blocs jusqu'à ce que le fil soit terminé.
Daniel
2
@HariKiran oui correct, chaque thread renvoie une valeur indépendante.
rootExplorr
28

Ce que vous recherchez est probablement l' Callable<V>interface à la place de Runnable, et la récupération de la valeur avec un Future<V>objet, ce qui vous permet également d'attendre que la valeur ait été calculée. Vous pouvez y parvenir avec un ExecutorService, à partir duquel vous pouvez obtenir 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
la source
8

Et cette solution?

Il n'utilise pas la classe Thread, mais il est simultané et, d'une certaine manière, il fait exactement ce que vous demandez

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

Maintenant, tout ce que vous faites est de dire value.get()que chaque fois que vous avez besoin de récupérer votre valeur retournée, le thread est lancé à la seconde où vous donnez valueune valeur afin que vous n'ayez jamais à dire là- threadName.start()dessus.

Quelle Futureest, est une promesse au programme, vous promettez le programme que vous obtenez la valeur dont il a besoin dans le futur proche

Si vous l'appelez .get()avant que ce soit fait, le thread qui l'appelle attendra simplement que ce soit terminé

Café électrique
la source
J'ai séparé le code fourni en deux choses, l'une est l'initiation de la piscine que je fais dans la classe Application (je parle d'Android) et deuxièmement, j'utilise la piscine là où j'en ai besoin ... Également en ce qui concerne l'utilisation d'Executors.newFixedThreadPool ( 2) j'ai utilisé Executors.newSingleThreadExecutor () .. car je n'avais besoin que d'une seule tâche à la fois pour les appels au serveur ... Votre réponse est parfaite café @electirc merci
Gaurav Pangam
@Electric comment savoir quand obtenir la valeur? Je crois que l'affiche originale voulait rendre cette valeur dans sa méthode. L'utilisation de l'appel .get () récupérera la valeur, mais uniquement si l'opération est terminée. Il ne le saurait pas avec un appel aveugle .get ()
portfoliobuilder
4

Si vous voulez la valeur de la méthode appelante, alors il doit attendre que le thread se termine, ce qui rend l'utilisation de threads un peu inutile.

Pour répondre directement à votre question, la valeur peut être stockée dans n'importe quel objet mutable auquel la méthode appelante et le thread ont tous deux une référence. Vous pouvez utiliser l'externe this, mais cela ne sera pas particulièrement utile à part pour des exemples triviaux.

Une petite note sur le code dans la question: l'extension Threadest généralement un style médiocre. En effet, étendre les classes inutilement est une mauvaise idée. Je remarque que votre runméthode est synchronisée pour une raison quelconque. Maintenant que l'objet dans ce cas est le, Threadvous pouvez interférer avec tout ce qui Threadutilise son verrou (dans l'implémentation de référence, quelque chose à voir avec join, IIRC).

Tom Hawtin - Tacle
la source
"alors il faut attendre que le thread se termine, ce qui rend l'utilisation des threads un peu inutile" super point!
likejudo
4
C'est généralement inutile, mais sous Android, vous ne pouvez pas faire de demande réseau à un serveur sur le thread principal (pour que l'application reste réactive), vous devrez donc utiliser un thread réseau. Il existe des scénarios dans lesquels vous avez besoin du résultat avant que l'application puisse reprendre.
Udi Idan
2

À partir de Java 8, nous avons CompletableFuture. Dans votre cas, vous pouvez utiliser la méthode supplyAsync pour obtenir le résultat après l'exécution.

Veuillez trouver des réf. ici https://www.baeldung.com/java-completablefuture#Processing

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

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

L'utilisation de Future décrite dans les réponses ci-dessus fait le travail, mais un peu moins significativement que f.get (), bloque le thread jusqu'à ce qu'il obtienne le résultat, ce qui viole la concurrence.

La meilleure solution est d'utiliser ListenableFuture de Guava. Un exemple :

    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
la source
1

Avec de petites modifications à votre code, vous pouvez y parvenir de manière plus générique.

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

Solution :

  1. Créez un Handlerthread dans l'interface utilisateur, qui s'appelleresponseHandler
  2. Initialisez ceci à Handlerpartir Looperde UI Thread.
  3. Dans HandlerThread, publier un message à ce sujetresponseHandler
  4. handleMessgaeaffiche une Toastavec valeur reçue du message. Cet objet Message est générique et vous pouvez envoyer différents types d'attributs.

Avec cette approche, vous pouvez envoyer plusieurs valeurs au thread d'interface utilisateur à différents moments. Vous pouvez exécuter (publier) de nombreux Runnableobjets sur celui-ci HandlerThreadet chacun Runnablepeut définir une valeur dans l' Messageobjet, qui peut être reçu par UI Thread.

Ravindra babu
la source