【问题标题】:Recursively cancel an allOf CompletableFuture递归取消 allOf CompletableFuture
【发布时间】:2017-04-13 10:32:00
【问题描述】:

如果我有

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

当我执行many.cancel() 时会发生什么? future1future2 也会被取消吗?如果不是,那么实现这一目标的最干净的方法是什么?我不愿意坚持future1future2,只是为了在我想取消many 时能够取消它们。

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

【问题讨论】:

  • 刚刚测试过:取消很多不会取消future1和future2。
  • 您需要取消内部期货,然后many 将与CancellationException[] 一起完成。
  • 我已经假设了,因为我一直在考虑这个问题:future1 可能是超过 1 个allOf 集合的一部分!所以它不是微不足道的,这可能就是它没有实施的原因......
  • 请记住:您是否取消many 或每个输入期货都无关紧要。效果是一样的。
  • @MProkhorov:“效果是一样的”并不意味着效果就是 OP 所想的。在这种情况下,它意味着在组件上调用 cancel 不太可能比在组合上调用 cancel 产生更多的效果。我已经在我的回答中详细说明了这个问题……

标签: java java-8 completable-future


【解决方案1】:

在让自己的生活变得不必要之前,您应该了解取消CompletableFuture 的实际作用。最重要的是,它不会停止相关的计算。

如果与CompletableFuture 关联的计算已经在运行,但尚未完成,取消CompletableFuture 会将其变为“已取消”状态,这可能会立即影响所有相关阶段,但不会影响计算将一直持续到完成,尽管它尝试完成已取消的未来不会有任何效果。

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

参数:

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

因此,当您成功取消future1future2 时,唯一的直接效果是取消many,您也可以通过在many 本身上调用cancel 来实现。如果有更多的依赖阶段,它会产生更广泛的影响,但既然你说你不想保留对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)或者你是通过true还是false;它不会停止正在进行的Supplier 评估。

如果相关的计算还没有开始,并且在开始时它会检查取消状态,就会有所不同,就像CompletableFuture中的便捷方法产生的操作一样。这是一种罕见的情况,通常情况下,您的 service.request(paramN) 调用应该会触发评估。

这是CompletableFuture 的基本属性,顾名思义,它是可完成的,即任何人都可以在其上调用complete,因此,CompletableFuture 无法控制将来可能最终会致电complete的人。因此,cancel 所能做到的就是将其设置为取消状态,这意味着忽略后续的完成尝试并将取消向下传播到相关操作。


所以最重要的是,您可能已经可以在 many 实例上调用 cancel,因为在 future1future2 上调用 cancel 不太可能产生值得的效果代码的复杂性。

【讨论】:

    【解决方案2】:

    CompletableFuture.allOf 构造的树不包含对CompletableFuture 的给定实例的任何引用。相反,如果只是构建 completion 树,即当所有给定的 CompletableFutures 完成时完成(来自JavaDocs)。

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

    【讨论】:

      【解决方案3】:

      你可以试试我的图书馆: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 都将被取消(如果尚未完成)。此外,如果单个期货中的任何一个异常完成,那么另一个期货将被自动取消(同样,如果尚未完成)。

      【讨论】:

      • 现在@Valery 的答案看起来好多了。我已经删除了我之前的评论,因为它不再相关。
      猜你喜欢
      • 2019-02-21
      • 1970-01-01
      • 2015-12-03
      • 1970-01-01
      • 2021-07-29
      • 2012-10-24
      • 1970-01-01
      • 1970-01-01
      • 2018-04-13
      相关资源
      最近更新 更多