Recursively cancel an allOf CompletableFuture

馋奶兔 提交于 2019-12-09 12:14:27

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!