递归取消allOf CompletableFuture

时间:2017-04-13 10:32:01

标签: java java-8 completable-future

如果我有

CompletableFuture<Something> future1 = service.request(param1);
CompletableFuture<Something> future2 = service.request(param2);
CompletableFuture<Void> many = CompletableFuture.allOf(future1, future2);

当我many.cancel()时会发生什么? future1future2也会被取消吗?如果没有,最简单的方法是什么?我不愿意继续future1future2,只是为了在我想取消many时取消它们。

关于我为什么要这样做的一些背景知识:在接收一条数据时,我需要请求匹配的,可能未来的数据来执行计算。如果有更新的数据到达,我想取消先前计算的完成,因为结果将立即被新计算取代。

3 个答案:

答案 0 :(得分:10)

在你让生活变得艰难之前,你应该知道取消CompletableFuture实际上做了什么。最重要的是,它停止相关的计算。

如果与CompletableFuture关联的计算已在运行但尚未完成,则取消CompletableFuture会将其转换为“已取消”状态,这可能对所有相关阶段产生直接影响,但不是计算,它将一直持续到完成,但它试图完成取消的未来将不会有任何影响。

虽然其他Future可能会被中断取消,这会停止计算,如果它检查中断,这不适用于CompletableFuture,请参阅CompletableFuture.cancel(boolean):< / p>

  

参数:

     

mayInterruptIfRunning - 此值在此实现中无效,因为中断不用于控制处理。

因此,当您成功取消future1future2时,唯一的直接影响就是取消many,您也可以通过调用cancel来取消在many本身。如果有更多的依赖阶段会产生更广泛的影响,但是既然你说过,你不想保留对future1future2的引用,那么情况似乎并非如此

以下代码演示了该行为:

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);

此代码可重复打印:

last stage: value: null, throwable: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException
canceling: true
supplying value

(订单可能会改变)

无论您是致电supply.cancel(true)还是then.cancel(true),还是通过truefalse;它不会停止正在进行的Supplier评估。

如果相关计算尚未开始并且在启动时确实检查取消状态,那将会有所不同,就像CompletableFuture中的便捷方法所产生的动作一样。这种情况很少见,通常情况下,您的service.request(paramN)电话会触发评估。

这是CompletableFuture的基本属性,顾名思义,它是可完成的,即任何人都可以在其上调用complete,因此{{1}无法控制将来可能最终调用CompletableFuture的人。因此,complete可以实现的是将其设置为已取消状态,这意味着忽略后续完成尝试并将取消向下传播到依赖操作。

所以最重要的是,您只需在cancel个实例上调用cancel就行了,因为在manycancel上调用future1不太可能产生值得代码复杂化的效果。

答案 1 :(得分:4)

CompletableFuture.allOf构建的树不会对CompletableFuture的给定实例进行任何引用。相反,如果只是构建完成树,那么在所有给定的CompletableFutures完成时完成(来自JavaDocs)。

因此,您可能必须保留对所有CompletableFuture的引用,以便在需要时按顺序取消它们。

答案 2 :(得分:0)

您可以试试我的图书馆:https://github.com/vsilaev/tascalate-concurrent

它提供了真正可取消的CompletionStage实现(CompletableTask)以及组合它们的方法(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);

现在您可以致电many.cancel(true)future1future2将被取消(如果尚未完成)。此外,如果个别期货中的任何一个异常完成,则另一个将自动取消(再次,如果尚未完成)。