Darksnake Darksnake - 4 months ago 26
Java Question

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.

Sam Sam
Answer

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);
        }
    }
}
Comments