J'ai peu de tâches asynchrones en cours d'exécution et je dois attendre jusqu'à ce qu'au moins l'une d'entre elles soit terminée (à l'avenir, je devrai probablement utiliser M sur N tâches). Actuellement, ils sont présentés comme Future, j'ai donc besoin de quelque chose comme
/**
* Blocks current thread until one of specified futures is done and returns it.
*/
public static <T> Future<T> waitForAny(Collection<Future<T>> futures)
throws AllFuturesFailedException
Y a-t-il quelque chose comme ça? Ou quelque chose de similaire, pas nécessaire pour Future. Actuellement, je passe en revue la collection de contrats à terme, vérifie si l'un est terminé, puis dors pendant un certain temps et vérifie à nouveau. Cela ne semble pas être la meilleure solution, car si je dors pendant une longue période, un délai indésirable est ajouté, si je dors pendant une courte période, cela peut affecter les performances.
Je pourrais essayer d'utiliser
new CountDownLatch(1)
et diminuer le compte à rebours lorsque la tâche est terminée et faire
countdown.await()
, mais je ne l'ai trouvé possible que si je contrôlais la création future. C'est possible, mais nécessite une refonte du système, car actuellement la logique de création des tâches (envoi de Callable à ExecutorService) est séparée de la décision d'attendre quel avenir. Je pourrais aussi passer outre
<T> RunnableFuture<T> AbstractExecutorService.newTaskFor(Callable<T> callable)
et créer une implémentation personnalisée de RunnableFuture avec la possibilité d'attacher l'écouteur pour être averti lorsque la tâche est terminée, puis attacher cet écouteur aux tâches nécessaires et utiliser CountDownLatch, mais cela signifie que je dois remplacer newTaskFor pour chaque ExecutorService que j'utilise - et potentiellement il y aura une implémentation qui n’étendent pas AbstractExecutorService. Je pourrais également essayer d'encapsuler ExecutorService donné dans le même but, mais je dois ensuite décorer toutes les méthodes produisant Futures.
Toutes ces solutions peuvent fonctionner mais semblent très peu naturelles. On dirait que je manque quelque chose de simple, comme
WaitHandle.WaitAny(WaitHandle[] waitHandles)
en c #. Existe-t-il des solutions bien connues pour ce type de problème?
METTRE À JOUR:
À l'origine, je n'avais pas du tout accès à la création Future, il n'y avait donc pas de solution élégante. Après avoir repensé le système, j'ai eu accès à la création future et j'ai pu ajouter countDownLatch.countdown () au processus d'exécution, puis je peux countDownLatch.await () et tout fonctionne bien. Merci pour les autres réponses, je ne connaissais pas ExecutorCompletionService et cela peut en effet être utile dans des tâches similaires, mais dans ce cas particulier, il n'a pas pu être utilisé car certains Futures sont créés sans aucun exécuteur - la tâche réelle est envoyée à un autre serveur via le réseau, se termine à distance et une notification d'achèvement est reçue.
Pour autant que je sache, Java n'a pas de structure analogue à la méthode WaitHandle.WaitAny
.
Il me semble que cela pourrait être réalisé grâce à un décorateur "WaitableFuture":
public WaitableFuture<T>
extends Future<T>
{
private CountDownLatch countDownLatch;
WaitableFuture(CountDownLatch countDownLatch)
{
super();
this.countDownLatch = countDownLatch;
}
void doTask()
{
super.doTask();
this.countDownLatch.countDown();
}
}
Bien que cela ne fonctionnerait que s'il pouvait être inséré avant le code d'exécution, sinon le code d'exécution n'aurait pas la nouvelle méthode doTask()
. Mais je ne vois vraiment aucun moyen de le faire sans interrogation si vous ne pouvez pas en quelque sorte prendre le contrôle de l'objet Future avant l'exécution.
Ou si l'avenir s'exécute toujours dans son propre thread, et vous pouvez en quelque sorte obtenir ce thread. Ensuite, vous pouvez générer un nouveau thread pour se rejoindre, puis gérer le mécanisme d'attente après le retour de la jointure ... Ce serait vraiment moche et induirait beaucoup de frais cependant. Et si certains objets Future ne se terminent pas, vous pourriez avoir beaucoup de threads bloqués en fonction des threads morts. Si vous ne faites pas attention, cela pourrait entraîner une fuite de mémoire et de ressources système.
/**
* Extremely ugly way of implementing WaitHandle.WaitAny for Thread.Join().
*/
public static joinAny(Collection<Thread> threads, int numberToWaitFor)
{
CountDownLatch countDownLatch = new CountDownLatch(numberToWaitFor);
foreach(Thread thread in threads)
{
(new Thread(new JoinThreadHelper(thread, countDownLatch))).start();
}
countDownLatch.await();
}
class JoinThreadHelper
implements Runnable
{
Thread thread;
CountDownLatch countDownLatch;
JoinThreadHelper(Thread thread, CountDownLatch countDownLatch)
{
this.thread = thread;
this.countDownLatch = countDownLatch;
}
void run()
{
this.thread.join();
this.countDownLatch.countDown();
}
}
simple, consultez ExecutorCompletionService .
Pourquoi ne pas simplement créer une file d'attente de résultats et attendre la file d'attente? Ou plus simplement, utilisez un CompletionService car c'est ce qu'il est: une file d'attente de résultats ExecutorService +.
C'est en fait assez facile avec wait () et notifyAll ().
Tout d'abord, définissez un objet verrou. (Vous pouvez utiliser n'importe quelle classe pour cela, mais j'aime être explicite):
package com.javadude.sample;
public class Lock {}
Ensuite, définissez votre thread de travail. Il doit notifier cet objet verrou lorsqu'il a terminé son traitement. Notez que la notification doit être dans un verrouillage de bloc synchronisé sur l'objet de verrouillage.
package com.javadude.sample;
public class Worker extends Thread {
private Lock lock_;
private long timeToSleep_;
private String name_;
public Worker(Lock lock, String name, long timeToSleep) {
lock_ = lock;
timeToSleep_ = timeToSleep;
name_ = name;
}
@Override
public void run() {
// do real work -- using a sleep here to simulate work
try {
sleep(timeToSleep_);
} catch (InterruptedException e) {
interrupt();
}
System.out.println(name_ + " is done... notifying");
// notify whoever is waiting, in this case, the client
synchronized (lock_) {
lock_.notify();
}
}
}
Enfin, vous pouvez écrire à votre client:
package com.javadude.sample;
public class Client {
public static void main(String[] args) {
Lock lock = new Lock();
Worker worker1 = new Worker(lock, "worker1", 15000);
Worker worker2 = new Worker(lock, "worker2", 10000);
Worker worker3 = new Worker(lock, "worker3", 5000);
Worker worker4 = new Worker(lock, "worker4", 20000);
boolean started = false;
int numNotifies = 0;
while (true) {
synchronized (lock) {
try {
if (!started) {
// need to do the start here so we grab the lock, just
// in case one of the threads is fast -- if we had done the
// starts outside the synchronized block, a fast thread could
// get to its notification *before* the client is waiting for it
worker1.start();
worker2.start();
worker3.start();
worker4.start();
started = true;
}
lock.wait();
} catch (InterruptedException e) {
break;
}
numNotifies++;
if (numNotifies == 4) {
break;
}
System.out.println("Notified!");
}
}
System.out.println("Everyone has notified me... I'm done");
}
}
Puisque vous ne vous souciez pas de celui qui se termine, pourquoi ne pas simplement avoir un seul WaitHandle pour tous les threads et attendre cela? Celui qui finit le premier peut régler la poignée.