Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 CompletableFuture lazy computation control

I've got a question about CompletableFuture and its possible usage for lazy computations.

It seems like it is a great substitute for RunnableFuture for this task since it is possible to easily create task chains and to have total control of each chain link. Still I found that it is very hard to control when exactly does the computation take place.

If I just create a CompletableFuture with supplyAssync method or something like that, it is OK. It waits patiently for me to call get or join method to compute. But if I try to make an actual chain with whenCompose, handle or any other method, the evaluation starts immediately, which is very frustrating.

Of course, I can always place some blocker task at the start of the chain and release the block when I am ready to begin calculation, but it seems a bit ugly solution. Does anybody know how to control when does CompletableFuture actually run.

like image 604
Alexander Nozik Avatar asked Jul 17 '16 11:07

Alexander Nozik


2 Answers

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.

like image 194
the8472 Avatar answered Oct 10 '22 00:10

the8472


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 the run 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);
        }
    }
}
like image 35
Sam Avatar answered Oct 09 '22 23:10

Sam