问题
If I have
CompletableFuture<Something> future1 = service.request(param1);
CompletableFuture<Something> future2 = service.request(param2);
CompletableFuture<Void> many = CompletableFuture.allOf(future1, future2);
what will happen when I do many.cancel()
? Will future1
and future2
be cancelled as well? If not, what would be the cleanest way to achieve this? I'm reluctant to hold on to future1
and future2
, just to be able to cancel them when I want to cancel many
.
Some background on why I want this: when receiving a piece of data, I need to request matching, potentially future data to perform a computation. If a newer piece of data arrives, I want to cancel the completion of the earlier computation, because the result will immediately be superceded by the new computation.
回答1:
Before you make you life harder than necessary, you should become aware of what cancelling a CompletableFuture
actually does. Most important, it does not stop the associated computation.
If a computation associated with a CompletableFuture
is already running, but has not completed yet, cancelling a CompletableFuture
turns it into the “cancelled” state, which may have an immediate effect on all dependent stages, but not on the computation, which will continue until complete, though its attempt to complete the cancelled future will not have any effect.
While other Future
’s might be cancelled with interruption, which will stop the computation, if it checks for interruption, this doesn’t apply to CompletableFuture
, see CompletableFuture.cancel(boolean):
Parameters:
mayInterruptIfRunning - this value has no effect in this implementation because interrupts are not used to control processing.
So when you cancel either, future1
or future2
, successfully, the only immediate effect would be the cancellation of many
, which you can also achieve by calling cancel
on many
itself. It would have a broader effect, if there were more dependent stages, but since you stated, that you don’t want to keep references to future1
or future2
, this doesn’t seem to be the case.
The following code demonstrates the behavior:
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
System.out.println("supplying value");
return "foo";
});
CompletableFuture<String> then = supply.thenApply(s -> {
System.out.println("Evaluating next stage");
return s;
});
CompletableFuture<?> last = then.handle((s,t) -> {
System.out.println("last stage: value: "+s+", throwable: "+t);
return "";
});
System.out.println("cancelling: "+supply.cancel(true));
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
This code reproducible prints:
last stage: value: null, throwable: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException
canceling: true
supplying value
(the order might change)
regardless of whether you call supply.cancel(true)
or then.cancel(true)
or whether you pass true
or false
; it won’t stop the ongoing Supplier
evaluation.
There will be a difference, if the associated computation hasn’t been started yet and it does check the cancellation state when starting, like with the actions produced by the convenience methods in CompletableFuture
. This is a rare situation, as normally, your service.request(paramN)
call is supposed to trigger the evaluation.
It’s a fundamental property of the CompletableFuture
, as its name suggests, that it is completable, i.e. anyone could call complete
on it, thus, the CompletableFuture
can’t control whoever might eventually call complete
on it in the future. So all, cancel
can achieve, is to set it to the cancelled state, which implies ignoring subsequent completion attempts and propagating the cancellation downward to the dependent actions.
So the bottom line is that you might already be fine with just calling cancel
on the many
instance, because calling cancel
on future1
and future2
is unlikely to have an effect that is worth the complication of your code.
回答2:
The tree constructed by CompletableFuture.allOf
doesn't hold any references to the given instances of CompletableFuture
. Instead if just builds completion tree, which is is completed when all of the given CompletableFutures complete (from JavaDocs).
So probably you have to keep references to all CompletableFuture
in order to cancel them sequentially when it is needed.
回答3:
You can give a try for my library: https://github.com/vsilaev/tascalate-concurrent
It provides both truly cancelable CompletionStage implementations (CompletableTask) as well as methods to combine them (Promises.all)
CompletionStage<Something> future1 = CompletableTask
.complete(param1, myExecutor).thenApplyAsync(this::serviceCall);
CompletionStage<Something> future2 = CompletableTask
.complete(param2, myExecutor).thenApplyAsync(this::serviceCall);
Promise<List<Something>> many = Promises.all(future1, future2);
Now you can call many.cancel(true)
and both future1
and future2
will be cancelled (if not yet completed). Moreover, if either of individual futures completes exceptionally, then another one will be cancelled automatically (again, if not yet completed).
来源:https://stackoverflow.com/questions/43389894/recursively-cancel-an-allof-completablefuture