web-dev-qa-db-fra.com

Dans quel thread les gestionnaires de complétion de CompletableFuture s'exécutent-ils?

J'ai une question sur la méthode CompletableFuture:

public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)

Le fait est que JavaDoc dit juste ceci:

Renvoie un nouveau CompletionStage qui, lorsque cette étape se termine normalement, est exécuté avec le résultat de cette étape comme argument de la fonction fournie. Reportez-vous à la documentation CompletionStage pour connaître les règles relatives à l'achèvement exceptionnel.

Et le filetage? Dans quel thread cela va-t-il être exécuté? Et si l'avenir est complété par un pool de threads?

23
St.Antario

Les politiques spécifiées dans les documents CompletableFuture pourraient vous aider à mieux comprendre:

  • Les actions fournies pour les compléments dépendants des méthodes non asynchrones peuvent être effectuées par le thread qui complète le CompletableFuture actuel, ou par tout autre appelant d'une méthode de complétion.

  • Toutes les méthodes asynchrones sans argument Executor explicite sont exécutées à l'aide de la fonction ForkJoinPool.commonPool() (sauf si elle ne prend pas en charge un niveau de parallélisme d'au moins deux, auquel cas, un nouveau thread est créé pour exécuter chaque tâche ). Pour simplifier la surveillance, le débogage et le suivi, toutes les tâches asynchrones générées sont des instances de l'interface de marqueur CompletableFuture.AsynchronousCompletionTask.

Mettre à jour: Je conseillerais également de lire cette réponse par @Mike comme intéressant une analyse plus approfondie des détails de la documentation.

21
Naman

Comme le souligne @ nullpointer , la documentation vous indique ce que vous devez savoir. Cependant, le texte pertinent est étonnamment vague, et certains des commentaires (et réponses) publiés ici semblent reposer sur des hypothèses qui ne sont pas étayées par la documentation. Ainsi, je pense qu'il vaut la peine de le distinguer. Plus précisément, nous devons lire ce paragraphe très attentivement:

Les actions fournies pour les compléments dépendants des méthodes non asynchrones peuvent être exécutées par le thread qui termine le CompletableFuture actuel, ou par tout autre appelant d'une méthode de complétion.

Cela semble assez simple, mais il est léger sur les détails. Cela évite apparemment délibérément de décrire quand une complétion dépendante peut être invoquée sur le thread de complétion par rapport à un appel à une méthode de complétion comme thenApply. Comme écrit, le paragraphe ci-dessus est pratiquement mendiant nous pour combler les lacunes avec des hypothèses. C'est dangereux, surtout lorsque le sujet concerne la programmation simultanée et asynchrone, où bon nombre des attentes que nous avons développées lorsque les programmeurs se tournent la tête. Examinons attentivement ce que la documentation ne dit pas .

La documentation ne pas prétend que les compléments dépendants enregistrés avant un appel à complete() s'exécutera sur le fil de fin. De plus, alors qu'il indique qu'une complétion dépendante pourrait être invoquée lors de l'appel d'une méthode de complétion comme thenApply, elle ne le fait pas indique qu'une complétion sera invoquée sur le thread qui l'enregistre (notez les mots "any other").

Ce sont des points potentiellement importants pour quiconque utilise CompletableFuture pour planifier et composer des tâches. Considérez cette séquence d'événements:

  1. Le thread A enregistre une complétion dépendante via f.thenApply(c1).
  2. Quelque temps plus tard, le thread B appelle f.complete().
  3. À peu près au même moment, le thread C enregistre une autre complétion dépendante via f.thenApply(c2).

Conceptuellement, complete() fait deux choses: il publie le résultat du futur, puis il tente d'appeler des compléments dépendants. Maintenant, que se passe-t-il si le thread C s'exécute après la valeur du résultat est publiée, mais avant le thread B se met à invoquer c1? Selon l'implémentation, le thread C peut voir que f est terminé, et il peut alors invoquer c1 etc2. Alternativement, le thread C peut invoquer c2 Tout en laissant le thread B invoquer c1. La documentation n'exclut aucune des deux possibilités. Dans cet esprit, voici des hypothèses que ne sont pas prises en charge par la documentation:

  1. Qu'une complétion dépendante c enregistrée sur f avant la complétion sera invoquée lors de l'appel à f.complete();
  2. Ce c aura été exécuté jusqu'à la fin du retour de f.complete();
  3. Ces compléments dépendants seront invoqués dans un ordre particulier (par exemple, ordre d'enregistrement);
  4. Les complétions dépendantes enregistrées avantf complétées seront appelées avant que les complétions enregistrées aprèsf se terminent.

Prenons un autre exemple:

  1. Le thread A appelle f.complete();
  2. Quelque temps plus tard, le thread B enregistre une complétion via f.thenApply(c1);
  3. À peu près au même moment, le thread C enregistre une complétion distincte via f.thenApply(c2).

Si l'on sait que f est déjà terminée, on pourrait être tenté de supposer que c1 Sera invoqué pendant f.thenApply(c1) et que c2 Sera être invoqué pendant f.thenApply(c2). On pourrait en outre supposer que c1 Se sera achevé au moment où f.thenApply(c1) reviendra. Cependant, la documentation ne prend pas en charge ces hypothèses. Il est possible que un des threads appelant thenApply finissent par invoquer les deuxc1 Et c2, tandis que l'autre thread n'appelle ni l'un ni l'autre.

Une analyse minutieuse du code JDK pourrait déterminer comment les scénarios hypothétiques ci-dessus pourraient se dérouler. Mais même cela est risqué, car vous pouvez vous fier à un détail d'implémentation (1) non portable ou (2) susceptible d'être modifié. Votre meilleur pari est de ne pas supposer quoi que ce soit qui ne soit pas précisé dans les javadocs ou les spécifications JSR d'origine.

tldr: Faites attention à ce que vous supposez, et lorsque vous écrivez de la documentation, soyez aussi clair et délibéré que possible. Bien que la brièveté soit une chose merveilleuse, méfiez-vous de la tendance humaine à combler les lacunes.

29
Mike Strobel

De la Javadoc :

Les actions fournies pour les compléments dépendants des méthodes non asynchrones peuvent être effectuées par le thread qui termine le CompletableFuture actuel, ou par tout autre appelant d'une méthode de complétion.

Plus concrètement:

  • fn s'exécutera pendant l'appel à complete() dans le contexte du thread qui a appelé complete().

  • Si complete() est déjà terminée au moment où thenApply() est appelée, fn sera exécutée dans le contexte du thread appelant thenApply().

7
NPE

En ce qui concerne le threading, la documentation de l'API fait défaut. Il faut un peu d'inférence pour comprendre comment fonctionnent les threads et les futurs. Commencez avec une hypothèse: les méthodes non -Async de CompletableFuture ne génèrent pas de nouveaux threads par elles-mêmes. Le travail se poursuivra sous les threads existants.

thenApply s'exécutera dans le thread d'origine de CompletableFuture. C'est soit le thread qui appelle complete(), soit celui qui appelle thenApply() si l'avenir est déjà terminé. Si vous voulez contrôler le thread - une bonne idée si fn est une opération lente - alors vous devez utiliser thenApplyAsync.

3
John Kugelman