2008-12-09 6 views
11

Selon vous, quel est le meilleur moyen d'obtenir les résultats du travail d'un fil? Imaginez un thread qui fait quelques calculs, comment avertissez-vous le programme principal les calculs sont faits?Obtenir la sortie d'un fil

Vous pouvez interroger toutes les X millisecondes pour une variable publique appelée "travail terminé" ou quelque chose d'autre, mais alors vous recevrez les résultats plus tard que quand ils seraient disponibles ... le code principal serait perdre du temps en les attendant. D'un autre côté, si vous utilisez un X inférieur, le processeur serait gaspillé à interroger tant de fois. Alors, que faites-vous pour être conscient que le fil, ou certains fils, ont terminé leur travail?

Désolé si elle ressemble à cette autre question, qui est probablement la raison de la Eben réponse, je suppose. Ce que je voulais dire, c'était courir beaucoup de threads et savoir quand ils ont tous fini, sans les interroger.

Je pensais plutôt à partager la charge CPU entre plusieurs processeurs en utilisant des lots de threads, et à savoir quand un batch est terminé. Je suppose que cela peut être fait avec s futurs objets, mais que le blocage get méthode ressemble beaucoup à un verrou caché, pas quelque chose que j'aime.

Merci à tous pour votre soutien. Althought J'ai aussi aimé la réponse par erickson, je pense que saua de la plus complète, et celui que je vais utiliser dans mon propre code.

+0

> et de savoir quand ab Atch a terminé. Pourquoi voulez-vous savoir cela? Avez-vous besoin de quelque chose de ce lot? Ou voulez-vous savoir quand vous pouvez réaliser plus de tâches? –

+0

Ceci est très similaire à cette [question] (http://stackoverflow.com/questions/289434/how-to-make-a-java-thread-wait-for-another-threads-output). – kgiannakakis

Répondre

25

N'utilisez pas de constructions de bas niveau, telles que des threads, sauf si vous avez absolument besoin de puissance et de flexibilité.

Vous pouvez utiliser un ExecutorService comme le ThreadPoolExecutor-submit()Callables. Cela renverra un objet Future. En utilisant cet objet Future, vous pouvez facilement vérifier si c'est fait et obtenir le résultat (y compris un blocage get() si ce n'est pas encore fait).

Ces constructions simplifieront grandement les opérations filetées les plus courantes.

Je voudrais préciser au sujet du blocage get():

L'idée est que vous voulez exécuter certaines tâches (les Callable s) qui font un travail (calcul, l'accès aux ressources, ...) où vous n'ont pas besoin du résultat maintenant. Vous pouvez simplement compter sur le Executor pour exécuter votre code quand il veut (si c'est un ThreadPoolExecutor alors il fonctionnera chaque fois qu'un fil libre est disponible). Ensuite, à un certain moment, vous avez probablement besoin de le résultat du calcul pour continuer. À ce stade, vous êtes censé appeler get(). Si la tâche a déjà été exécutée à ce moment-là, alors get() retournera simplement la valeur immédiatement. Si la tâche ne s'est pas terminée, l'appel get() attendra jusqu'à ce que la tâche soit terminée. Ceci est généralement souhaité car vous ne pouvez pas continuer sans le résultat des tâches de toute façon.

Lorsque vous n'avez pas besoin de la valeur de continuer, mais que vous aimeriez savoir à ce sujet si elle est déjà disponible (peut-être de montrer quelque chose dans l'interface utilisateur), alors vous pouvez facilement appeler isDone() et seulement appeler get() si cela retourne true).

1

Interroger une attente en attente n'est pas une bonne idée. Comme vous l'avez mentionné, l'attente en cours gaspille des cycles de processeur et peut provoquer l'affichage de votre application sans réponse.

Mon Java est rude, mais vous voulez quelque chose comme ce qui suit:

Si un thread doit attendre la sortie d'un autre fil que vous devez utiliser une variable de condition.

final Lock lock = new ReentrantLock(); 
final Condition cv = lock.newCondition(); 

Le fil intéressé à la sortie de l'autre menace devrait appeler cv.wait(). Cela entraînera le thread en cours à bloquer. Lorsque le thread de travail est terminé en cours d'exécution, il doit appeler cv.signal(). Cela entraînera le déblocage du thread bloqué, ce qui lui permettra d'inspecter la sortie du thread de travail.

0

Comme noté par saua: utilisez les constructions offertes par java.util.concurrent. Si vous êtes bloqué avec un JRE pré-1.5 (ou 5.0), vous pouvez avoir recours à un type de roulement, mais vous êtes toujours mieux en utilisant un backport: http://backport-jsr166.sourceforge.net/

1

Comme alternative à l'API de concurrence comme décrit par Saua (et si le thread principal n'a pas besoin de savoir quand un thread de travail se termine), vous pouvez utiliser le modèle publish/subscribe.

Dans ce scénario, l'enfant Thread/Runnable est donné un auditeur qui sait comment traiter le résultat et qui est appelé à quand l'enfant Thread/Runnable finalise.

1

Votre scénario est encore un peu flou. Si vous exécutez un travail par lots, vous pouvez utiliser invokeAll. Cela bloquera votre thread principal jusqu'à ce que toutes les tâches soient terminées. Il n'y a pas d'attente "occupée" avec cette approche, où le thread principal gaspillerait la CPU à interroger la méthode isDone d'un avenir. Bien que cette méthode renvoie une liste de Futures, ils sont déjà "terminés". (Il y a aussi une version surchargée qui peut expirer avant l'achèvement, ce qui peut être plus sûr à utiliser avec certaines tâches.) Cela peut être beaucoup plus propre que d'essayer de rassembler des objets Future et d'essayer de vérifier leur statut ou de les bloquer. get méthodes individuellement.

S'il s'agit d'une application interactive, avec des tâches sporadiquement dérivées à exécuter en arrière-plan, l'utilisation d'un callback comme suggéré par nick.holt est une excellente approche. Ici, vous utilisez le submit un Runnable. La méthode run appelle le rappel avec le résultat lorsqu'il a été calculé. Avec cette approche, vous pouvez ignorer le Future renvoyé par submit, sauf si vous voulez pouvoir exécuter des tâches cancel sans fermer l'ensemble ExecutorService.

Si vous souhaitez pouvoir annuler des tâches ou utiliser les fonctionnalités de temporisation, il est important de se souvenir que les tâches sont annulées en appelant interrupt dans leur tâche. Ainsi, votre tâche doit vérifier son interrupted status périodiquement et annuler au besoin.

1

Discussion sous-catégorie, et donner à votre classe une méthode qui renvoie le résultat. Lorsque la méthode est appelée, si le résultat n'a pas encore été créé, alors rejoignez() avec le Thread. Quand join() revient, le travail de votre Thread sera fait et le résultat devrait être disponible; rends le.

Utilisez ce que si vous avez réellement besoin de déclencher une activité asynchrone, faire un peu de travail pendant que vous êtes en attente, puis obtenir le résultat. Sinon, quel est le but d'un fil? Vous pourriez aussi bien écrire une classe qui fait le travail et retourne le résultat dans le fil principal.

Une autre approche serait un rappel: que votre constructeur prend un argument qui implémente une interface avec une méthode de rappel qui sera appelée lorsque le résultat est calculé. Cela rendra le travail complètement asynchrone. Mais si vous devez attendre le résultat à un moment donné, je pense que vous aurez toujours besoin d'appeler join() à partir du thread principal.

4

Vous pouvez créer une interface que le programme listeuse principal met en œuvre Wich est appelé par le travailleur une fois qu'il a terminé l'exécution de ses travaux.

De cette façon, vous ne avez pas besoin de sondage du tout.

Voici une interface exemple:

/** 
* Listener interface to implement to be called when work has 
* finished. 
*/ 
public interface WorkerListener { 
    public void workDone(WorkerThread thread); 
} 

Voici un exemple du fil réel qui fait un travail et en informe son auditoire:

import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 

/** 
* Thread to perform work 
*/ 
public class WorkerThread implements Runnable { 
    private List listeners = new ArrayList(); 
    private List results; 

    public void run() { 
     // Do some long running work here 

     try { 
      // Sleep to simulate long running task 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

     results = new ArrayList(); 
     results.add("Result 1"); 

     // Work done, notify listeners 
     notifyListeners(); 
    } 

    private void notifyListeners() { 
     for (Iterator iter = listeners.iterator(); iter.hasNext();) { 
      WorkerListener listener = (WorkerListener) iter.next(); 
      listener.workDone(this); 
     } 
    } 

    public void registerWorkerListener(WorkerListener listener) { 
     listeners.add(listener); 
    } 

    public List getResults() { 
     return results; 
    } 
} 

Et enfin, le programme principal qui démarre un thread de travail et enregistre un auditeur pour être averti une fois le travail terminé:

import java.util.Iterator; 
import java.util.List; 

/** 
* Class to simulate a main program 
*/ 
public class MainProg { 
    public MainProg() { 
     WorkerThread worker = new WorkerThread(); 
     // Register anonymous listener class 
     worker.registerWorkerListener(new WorkerListener() { 
      public void workDone(WorkerThread thread) { 
       System.out.println("Work done"); 
       List results = thread.getResults(); 
       for (Iterator iter = results.iterator(); iter.hasNext();) { 
        String result = (String) iter.next(); 
        System.out.println(result); 
       } 
      } 
     }); 

     // Start the worker thread 
     Thread thread = new Thread(worker); 
     thread.start(); 

     System.out.println("Main program started"); 
    } 

    public static void main(String[] args) { 
     MainProg prog = new MainProg(); 
    } 
} 
Questions connexes