web-dev-qa-db-fra.com

Renvoyer de la valeur de thread

J'ai une méthode avec un HandlerThread. Une valeur est modifiée dans la Thread et j'aimerais la renvoyer à la méthode test(). 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();
}
82
Neeta

Vous pouvez utiliser un tableau de variables final local. La variable doit être de type non primitif pour que vous puissiez utiliser un tableau. Vous devez également synchroniser les deux threads, par exemple en utilisant 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 Executor et un Callable comme 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();
}
68
Adam Zalcman

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;dr un thread ne peut pas renvoyer de valeur (du moins sans mécanisme de rappel). Vous devriez référencer un thread comme une classe ordinaire et demander la valeur.

66
Johan Sjöberg

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

Que diriez-vous de cette solution?

Il n'utilise pas la classe Thread, mais il IS simultané et fait en quelque sorte 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() chaque fois que vous devez saisir votre valeur renvoyée, le fil est démarré à la seconde où vous donnez à value une valeur afin que vous n'ayez jamais à dire threadName.start(). dessus.

Qu'est-ce qu'un Future est, est un promesse au programme, vous promettez au programme que vous obtiendrez la valeur dont il a besoin dans un avenir proche

Si vous appelez .get() avant la fin de l'opération, le fil qui l'appelle attendra simplement jusqu'à ce que l'opération soit terminée.

6
Electric Coffee

Si vous voulez la valeur de la méthode d'appel, attendez que le thread se termine, ce qui rend l'utilisation des threads un peu inutile.

Pour répondre directement à votre question, la valeur peut être stockée dans n’importe quel objet mutable, la méthode d’appel et le thread ayant tous deux une référence. Vous pouvez utiliser la variable this extérieure, mais cela ne sera pas particulièrement utile, sauf pour des exemples triviaux.

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

4

Java 8 fournit CompletableFuture. ce sera la solution unique pour cela. http://www.baeldung.com/Java-completablefuture

1
Vinto

Utiliser Future décrit dans les réponses ci-dessus fait le travail, mais un peu moins significatif que f.get (), bloque le thread jusqu'à ce qu'il obtienne le résultat, ce qui viole l'accès simultané.

La meilleure solution consiste à 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)
        {

        }
    };
1
bitsabhi

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 Handler dans le thread de l'interface utilisateur, appelé responseHandler.
  2. Initialisez cette Handler à partir de Looper du thread UI.
  3. Dans HandlerThread, postez un message sur cette responseHandler
  4. handleMessgae affiche un Toast avec la 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 à un thread d'interface utilisateur à des moments différents. Vous pouvez exécuter (publier) de nombreux objets Runnable sur ce HandlerThread et chaque Runnable peut définir une valeur dans un objet Message pouvant être reçu par UI Thread.

0
Ravindra babu