Reputation: 3260
Folks I have been using CompletableFuture in my project and I experienced a strange behaviour. I would like to understand the behaviour. Kindly help
Scenario 1: In the below code the output is I am from supply I am the first thenApply I am the second thenApply .. As expected
public void callAll(){
String calculatedDistance = listBuildComp().stream()
.map(CompletableFuture::join)
.collect(Collectors.joining());
System.out.println(calculatedDistance);
}
private List<CompletableFuture<String>> listBuildComp(){
List<CompletableFuture<String>> result = new ArrayList<>();
result.add(buildComp());
return result;
}
private CompletableFuture<String> buildComp(){
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ")
.thenApply( x -> {
return x.concat(" I am the first thenApply ");
})
.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return workFlowWithServices;
}
Scenario 2: When the below method is changed then the output is I am from supply . Upon further investigation I see that the rest two thenApply runs in their own thread
private CompletableFuture<String> buildComp(){
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ");
workFlowWithServices.thenApply( x -> {
return x.concat(" I am the first thenApply ");
});
workFlowWithServices.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return workFlowWithServices;
}
The reason I am interested with Scenario 2 is imagine you are chaining 2 TASKS then Scenario 1 is okay but imagine you want to chain 50 TASKS then the method will get too big. In this case I wanted to extract each calls in to a method to begin with but eventually extract in to a class if required but I cannot do all these cause of Scenario 2.
Want to know the concept or idea about why scenario 2 behaves in a different way and if there is anyway to make it behave like scenario 1. Kindly share your knowledge. Thank you.
Upvotes: 1
Views: 598
Reputation: 121048
First of all, you have no guarantee which thread will execute those thenApply
, it could easily be main
.
Then in your example, you build a CompletableFuture
:
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ");
chain some actions :
workFlowWithServices.thenApply( x -> {
System.out.println("executing");
return x.concat(" I am the first thenApply ");
})
...
but you ignore the result of that thenApply
(which is a CompletableFuture<String>
too). When you join
, you join
on workFlowWithServices
which, when it's done, will return "I am from supply "
. Done. You do not query (you ignore entirely) the result of subsequent actions in thenApply
, thus they do execute, but the result is gone.
I do not get what exactly stops you to build something like this, for example:
private static CompletableFuture<String> buildComp2(){
CompletableFuture<String> one =
CompletableFuture.supplyAsync( () -> "I am from supply ");
CompletableFuture<String> two = one.thenApply( x -> {
System.out.println("executing");
return x.concat(" I am the first thenApply ");
});
CompletableFuture<String> three = two.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return three;
}
Upvotes: 2