web-dev-qa-db-fra.com

ExecutorCompletionService? Pourquoi en avons-nous besoin si nous avons invokeAll?

Si nous utilisons un ExecutorCompletionService nous pouvons soumettre une série de tâches en tant que Callables et obtenir le résultat en interaction avec le CompletionService en tant que queue.

Mais il y a aussi le invokeAll de ExecutorService qui accepte un Collection de tâches et nous obtenons une liste de Future pour récupérer les résultats.

Pour autant que je sache, il n'y a aucun avantage à utiliser l'un ou l'autre (sauf que nous évitons une boucle for en utilisant un invokeAll que nous aurions à submit les tâches au CompletionService) et essentiellement ce sont la même idée avec une légère différence.

Alors pourquoi y a-t-il 2 façons différentes de soumettre une série de tâches? Ai-je raison de dire que les performances sont équivalentes? Y a-t-il un cas où l'un est plus approprié que l'autre? Je ne peux pas penser à un.

38
Cratylus

Utilisant un ExecutorCompletionService.poll/take, vous recevez les Futures à la fin, dans l'ordre d'achèvement (plus ou moins). En utilisant ExecutorService.invokeAll, vous n'avez pas ce pouvoir; soit vous bloquez jusqu'à ce que tous soient terminés, soit vous spécifiez un délai après lequel les incomplets sont annulés.


static class SleepingCallable implements Callable<String> {

  final String name;
  final long period;

  SleepingCallable(final String name, final long period) {
    this.name = name;
    this.period = period;
  }

  public String call() {
    try {
      Thread.sleep(period);
    } catch (InterruptedException ex) { }
    return name;
  }
}

Maintenant, ci-dessous, je vais montrer comment fonctionne invokeAll:

final ExecutorService pool = Executors.newFixedThreadPool(2);
final List<? extends Callable<String>> callables = Arrays.asList(
    new SleepingCallable("quick", 500),
    new SleepingCallable("slow", 5000));
try {
  for (final Future<String> future : pool.invokeAll(callables)) {
    System.out.println(future.get());
  }
} catch (ExecutionException | InterruptedException ex) { }
pool.shutdown();

Cela produit la sortie suivante:

C:\dev\scrap>Java CompletionExample
... after 5 s ...
quick
slow

En utilisant CompletionService, nous voyons une sortie différente:

final ExecutorService pool = Executors.newFixedThreadPool(2);
final CompletionService<String> service = new ExecutorCompletionService<String>(pool);
final List<? extends Callable<String>> callables = Arrays.asList(
    new SleepingCallable("slow", 5000),
    new SleepingCallable("quick", 500));
for (final Callable<String> callable : callables) {
  service.submit(callable);
}
pool.shutdown();
try {
  while (!pool.isTerminated()) {
    final Future<String> future = service.take();
    System.out.println(future.get());
  }
} catch (ExecutionException | InterruptedException ex) { }

Cela produit la sortie suivante:

C:\dev\scrap>Java CompletionExample
... after 500 ms ...
quick
... after 5 s ...
slow

Notez que les heures sont relatives au démarrage du programme, pas au message précédent.


Vous pouvez trouver le code complet sur les deux ici .

75
oldrinb

En utilisant un ExecutorCompletionService, vous pouvez être immédiatement averti lorsque chacun de vos travaux est terminé. En comparaison, ExecutorService.invokeAll(...) attend la fin de tous de vos travaux avant de renvoyer la collection de Futures:

// this waits until _all_ of the jobs complete
List<Future<Object>> futures = threadPool.invokeAll(...);

Au lieu de cela, lorsque vous utilisez un ExecutorCompletionService, vous pourrez obtenir les travaux dès que chacun d'eux se termine, ce qui vous permet (par exemple) de les envoyer pour traitement dans un autre pool de threads, les résultats du journal, etc. ..

ExecutorService threadPool = Executors.newFixedThreadPool(2);
ExecutorCompletionService<Result> compService
      = new ExecutorCompletionService<Result>(threadPool);
for (MyJob job : jobs) {
    compService.submit(job);
}
// shutdown the pool but the jobs submitted continue to run
threadPool.shutdown();
while (!threadPool.isTerminated()) {
    // the take() blocks until any of the jobs complete
    // this joins with the jobs in the order they _finish_
    Future<Result> future = compService.take();
    // this get() won't block
    Result result = future.get();
    // you can then put the result in some other thread pool or something
    // to immediately start processing it
    someOtherThreadPool.submit(new SomeNewJob(result));
}

Je n'ai jamais réellement utilisé ExecutorCompletionService, mais je pense que le cas où cela pourrait être plus utile que ExecutorService "normal" serait lorsque vous souhaitez recevoir les Futures des tâches terminées dans l'ordre d'achèvement. Avec invokeAll, vous obtenez simplement une liste qui peut contenir un mélange de tâches incomplètes et terminées à un moment donné.

3
esaj

Comparer en ne considérant que l'ordre des résultats:

Lorsque nous utilisons CompletionService chaque fois qu'un travail soumis est terminé, le résultat est poussé dans la file d'attente (Ordre d'achèvement). Ensuite, l'ordre des travaux soumis et les résultats retournés ne sont plus les mêmes. Donc, si vous êtes préoccupé par l'ordre dans lequel les tâches ont été exécutées, utilisez CompletionService

Où As invokeAll renvoie une liste de Futures représentant les tâches, dans le même ordre séquentiel que celui produit par l'itérateur pour la liste de tâches donnée, chacune étant terminée.

1
Dungeon Hunter