Supposons que j'ai le code suivant:
CompletableFuture<Integer> future
= CompletableFuture.supplyAsync( () -> 0);
thenApply
cas:
future.thenApply( x -> x + 1 )
.thenApply( x -> x + 1 )
.thenAccept( x -> System.out.println(x));
Ici, le résultat sera 2. Maintenant, dans le cas de thenApplyAsync
:
future.thenApplyAsync( x -> x + 1 ) // first step
.thenApplyAsync( x -> x + 1 ) // second step
.thenAccept( x -> System.out.println(x)); // third step
J'ai lu dans ce blog que chaque thenApplyAsync
est exécutée dans un thread séparé et 'en même temps' (c'est-à-dire après thenApplyAsyncs
commencé avant précédent thenApplyAsyncs
finish), si c'est le cas, quelle est la valeur d'argument d'entrée de la deuxième étape si la première étape n'est pas terminée?
Où ira le résultat de la première étape s'il n'est pas pris par la deuxième étape? la troisième étape va prendre le résultat de quelle étape?
Si la deuxième étape doit attendre le résultat de la première étape, alors quel est l'intérêt de Async
?
Ici x -> x + 1 est juste pour montrer le point, ce que je veux savoir, c’est dans les cas de très long calcul.
La différence concerne la Executor
responsable de l'exécution du code. Chaque opérateur sur CompletableFuture
a généralement 3 versions.
thenApply(fn)
- exécute fn
sur un thread défini par la CompleteableFuture
sur laquelle elle est appelée, de sorte que vous ne pouvez généralement pas savoir où elle sera exécutée. Il peut s'exécuter immédiatement si le résultat est déjà disponible.thenApplyAsync(fn)
- exécute fn
sur un exécuteur défini par l'environnement, quelles que soient les circonstances. Pour CompletableFuture
, ce sera généralement ForkJoinPool.commonPool()
.thenApplyAsync(fn,exec)
- exécute fn
sur exec
.En fin de compte, le résultat est identique, mais le comportement de planification dépend du choix de la méthode.
Je dois souligner que les noms thenApply
et thenApplyAsync
sont absolument horribles et déroutants. Il n'y a rien dans thenApplyAsync
qui soit plus asynchrone que thenApply
du contrat de ces méthodes.
La différence concerne le thread sur lequel la fonction est exécutée. La fonction fournie à thenApply
peut s’exécuter sur l’un des threads that
complete
thenApply
sur la même instancealors que thenApplyAsync
utilise soit une Executor
par défaut (pool de threads), soit une Executor
fournie.
La partie asynchrone de ces fonctions concerne le fait qu’une opération asynchrone appelle finalement complete
ou completeExceptionally
. L'idée vient de Javascript, qui n'a rien à voir avec le multithreading.
C’est ce que dit la documentation sur CompletableFuture's
thenApplyAsync
:
Retourne un nouveau CompletionStage qui, à la fin de cette étape normalement, est exécuté en utilisant la valeur asynchrone par défaut de cette étape fonction d'exécution, avec le résultat de cette étape comme argument de la fonction fournie.
Donc, thenApplyAsync
doit attendre le résultat précédent thenApplyAsync's
:
Dans votre cas, vous effectuez d’abord le travail synchrone, puis asynchrone. Ainsi, peu importe que le second soit asynchrone car il est démarré uniquement une fois le travail synchrone terminé.
Allumons-le. Dans certains cas, "résultat asynchrone: 2" sera imprimé en premier et dans certains cas, "résultat de synchronisation: 2" sera imprimé en premier. Cela fait une différence, car les appels 1 et 2 peuvent être exécutés de manière asynchrone, appeler 1 sur un thread séparé et 2 sur un autre thread, qui peut être le thread principal.
CompletableFuture<Integer> future
= CompletableFuture.supplyAsync(() -> 0);
future.thenApplyAsync(x -> x + 1) // call 1
.thenApplyAsync(x -> x + 1)
.thenAccept(x -> System.out.println("async result: " + x));
future.thenApply(x -> x + 1) // call 2
.thenApply(x -> x + 1)
.thenAccept(x -> System.out.println("sync result:" + x));