Java 8 CompletableFuture de calcul paresseux de contrôle
J'ai une question sur CompletableFuture et son utilisation possible pour les calculs paresseux.
Il semble que ce soit un excellent substitut à RunnableFuture
pour cette tâche car il est possible de créer facilement des chaînes de tâches et d'avoir le contrôle total de chaque maillon de la chaîne. Pourtant, j'ai trouvé qu'il est très difficile de contrôler quand exactement le calcul a-t-il lieu.
Si je crée juste un CompletableFuture
avec la méthode supplyAssync
ou quelque chose comme ça, c'est OK. Il attend patiemment que j'appelle get
ou join
méthode de calcul. Mais si j'essaie de faire une chaîne réelle avec whenCompose
, handle
ou toute autre méthode, l'évaluation commence immédiatement, ce qui est très frustrant.
Bien sûr, je peux toujours placer une tâche de blocage au début de la chaîne et libérer le bloc lorsque je suis prêt à commencer le calcul, mais cela semble une solution un peu moche. Est-ce que quelqu'un sait comment contrôler quand CompletableFuture
s'exécute réellement.
3 answers
Il y a une différence conceptuelle entre RunnableFuture
et CompletableFuture
qui vous manque ici.
-
RunnableFuture
les implémentations prennent une tâche en entrée et la conservent. Il exécute la tâche lorsque vous appelez larun
méthode. - A
CompletableFuture
ne tient pas sur une tâche. Il connait le résultat d'une tâche. Il y a trois états: complète, incomplète, et terminé exceptionnellement (échec).
CompletableFuture.supplyAsync
est une méthode d'usine qui vous donne un incomplet CompletableFuture
. Il planifie également une tâche qui, une fois terminée, transmettra son résultat à la méthode CompletableFuture
's complete
. En d'autres termes, l'avenir que supplyAsync
vous tend ne sait rien de la tâche et ne peut pas contrôler quand la tâche s'exécute.
Pour utiliser un CompletableFuture
dans la façon dont vous décrivez, vous devez créer une sous-classe:
public class RunnableCompletableFuture<T> extends CompletableFuture<T> implements RunnableFuture<T> {
private final Callable<T> task;
public RunnableCompletableFuture(Callable<T> task) {
this.task = task;
}
@Override
public void run() {
try {
complete(task.call());
} catch (Exception e) {
completeExceptionally(e);
}
}
}
Une façon simple de traiter votre problème consiste à envelopper votre CompletableFuture dans quelque chose de nature paresseuse. Vous pouvez utiliser un fournisseur ou même un flux Java 8.
CompletableFuture est un push-design, c'est-à-dire que les résultats sont poussés vers les tâches dépendantes dès qu'ils sont disponibles. Cela signifie également que les chaînes secondaires qui ne sont pas consommées en elles-mêmes sont toujours exécutées, ce qui peut avoir des effets secondaires.
Ce que vous voulez, c'est un pull-design où les ancêtres ne seraient extraits que lorsque leurs données sont consommées. Ce serait une conception fondamentalement différente car les effets secondaires des arbres non consommés ne se produiraient jamais.
Bien sûr avec assez les contorsions CF pourraient être faites pour faire ce que vous voulez, mais vous devriez plutôt regarder dans le cadre fork-join qui vous permet de n'exécuter que les calculs dont vous dépendez au lieu de réduire les résultats.