So I have a method that returns a CompletableFuture
. Before returning, this method adds a block with thenAccept
which is executed after the CompletableFuture
completes.
The caller of this method also adds another block with thenAccept
. Obviously this can go on with multiple chained calls.
In what order are the CompletionStage
returned by the thenAccept
invocations executed? Is it guaranteed to be the order in which they are added? If not, how can one guarantee that they are executed in the order in which they are added?
PS: I am asking this based on my own experience with CompletableFuture
and this article
You are modelling the dependencies of completion stages by chaining them. If you chain two actions A
and B
to another action C
, you define the relationships A → C
and B → C
, but no relationship between A
and B
and therefore, there is no relationship, including no ordering relationship, between them, in other words, you can’t even assume that one will run after the other, e.g.
CompletableFuture<String> base=CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
return "source";
});
base.thenAccept(s -> {
System.out.println("entered first consumer in "+Thread.currentThread());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("leaving first consumer");
});
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
base.thenAccept(s -> {
System.out.println("entered second consumer in "+Thread.currentThread());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("leaving second consumer");
});
will very likely print something like
entered first consumer in Thread[ForkJoinPool.commonPool-worker-1,5,main]
entered second consumer in Thread[main,5,main]
leaving second consumer
leaving first consumer
Though, of course, there is no guarantee about it.
To enforce your dependency between the two consumers, you have to chain them appropriately, e.g.
CompletableFuture<String> base=CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
return "source";
});
CompletableFuture<Void> next = base.thenAccept(s -> {
System.out.println("entered first consumer in "+Thread.currentThread());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("leaving first consumer");
});
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
base.thenAcceptBoth(next, (s,ignored) -> {
System.out.println("entered second consumer in "+Thread.currentThread());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println("leaving second consumer");
}).join();
Here, the second consumer is chained to base
and next
, to receive the result from base
, but depend on next
’s completion (which you normally wouldn’t require if there is no result to pass—maybe you want to rethink your design, if you have such a scenario).
Alternatively, you may convert the first Consumer
to a Function
which passes through the value, so you can chain it via thenApply
, to allow chaining another thenAccept
stage to it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With