Java 8 CompletableFuture lazy computation control
CompletableFuture is a push-design, i.e. results are pushed down to dependent tasks as soon as they become available. This also means side-chains that are not in themselves consumed still get executed, which can have side-effects.
What you want is a pull-design where ancestors would only be pulled in as their data is consumed. This would be a fundamentally different design because side-effects of non-consumed trees would never happen.
Of course with enough contortions CF could be made to do what you want, but you should look into the fork-join framework instead which allows you to only run the computations you depend on instead of pushing down results.
There's a conceptual difference between RunnableFuture
and CompletableFuture
that you're missing here.
RunnableFuture
implementations take a task as input and hold onto it. It runs the task when you call therun
method.- A
CompletableFuture
does not hold onto a task. It only knows about the result of a task. It has three states: complete, incomplete, and completed exceptionally (failed).
CompletableFuture.supplyAsync
is a factory method that gives you an incomplete CompletableFuture
. It also schedules a task which, when it completes, will pass its result to the CompletableFuture
's complete
method. In other words, the future that supplyAsync
hands you doesn't know anything about the task, and can't control when the task runs.
To use a CompletableFuture
in the way you describe, you would need to create a subclass:
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);
}
}
}