ulab
ulab

Reputation: 1089

defer thenApplyAsync execution

I have following scenario.

CompletableFuture<T> result = CompletableFuture.supplyAsync(task, executor);
result.thenRun(() -> {
        ...
    });
// ....
// after some more code, based on some condition I attach the thenApply() to result.

if ( x == 1) {
    result.thenApplyAsync(t -> {

        return null;
    });
}

The question is what if the CompletableFuture thread finishes the execution before the main thread reaches the thenApplyAsync ? does the CompletableFuture result shall attach itself to thenApply. i.e should callback be declared at the time of defining CompletableFuture.supplyAsync() itself ?

Also what is the order of execution ? thenRun() is always executed at last (after thenApply()) ?

Is there any drawback to use this strategy?

Upvotes: 2

Views: 1246

Answers (1)

Holger
Holger

Reputation: 298233

You seem to be missing an important point. When you chain a dependent function, you are not altering the future you’re invoking the chaining method on.

Instead, each of these methods returns a new completion stage representing the dependent action.

Since you are attaching two dependent actions to result, which represent the task passed to supplyAsync, there is no relationship between these two actions. They may run in an arbitrary order and even at the same time in different threads.

Since you are not storing the future returned by thenApplyAsync anywhere, the result of its evaluation would be lost anyway. Assuming that your function returns a result of the same type as T, you could use

if(x == 1) {
    result = result.thenApplyAsync(t -> {

        return null;
    });
}

to replace the potentially completed future with the new future that only gets completed when the result of the specified function has been evaluated. The runnable registered at the original future via thenRun still does not depend on this new future. Note that thenApplyAsync without an executor will always use the default executor, regardless of which executor was used to complete the other future.

If you want to ensure that the Runnable has been successfully executed before any other stage, you can use

CompletableFuture<T> result = CompletableFuture.supplyAsync(task, executor);
CompletableFuture<Void> thenRun = result.thenRun(() -> {
    //...
});
result = result.thenCombine(thenRun, (t,v) -> t);

An alternative would be

result = result.whenComplete((value, throwable) -> {
    //...
});

but here, the code will be always executed even in the exceptional case (which includes cancellation). You would have to check whether throwable is null, if you want to execute the code only in the successful case.


If you want to ensure that the runnable runs after both actions, the simplest strategy would be to chain it after the if statement, when the final completion stage is defined:

if(x == 1) {
    result = result.thenApplyAsync(t -> {

        return null;
    });
}
result.thenRun(() -> {
    //...
});

If that is not an option, you would need an incomplete future which you can complete on either result:

CompletableFuture<T> result = CompletableFuture.supplyAsync(task, executor);

//...

CompletableFuture<T> finalStage = new CompletableFuture<>();
finalStage.thenRun(() -> {
    //...
});

// ...

if(x == 1) {
    result = result.thenApplyAsync(t -> {

        return null;
    });
}
result.whenComplete((v,t) -> {
    if(t != null) finalStage.completeExceptionally(t); else finalStage.complete(v);
});

The finalStage initially has no defined way of completion, but we can still chain dependent actions. Once we know the actual future, we can chain a handler which will complete our finalStage with whatever result we have.


As a final note, the methods without …Async, like thenRun, provide the least control over the evaluation thread. They may get executed in whatever thread completed the future, like one of executor’s threads in your example, but also directly in the thread calling thenRun, and even less intuitive, in your original example, the runnable may get executed during the unrelated thenApplyAsync invocation.

Upvotes: 4

Related Questions