Reputation: 1089
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
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