【问题标题】:Interrupt CompletableFuture with default value使用默认值中断 CompletableFuture
【发布时间】:2017-10-20 23:47:07
【问题描述】:

假设我有 3 个服务。 首先我调用serviceA,它返回一个CompletableFuture。 之后我调用serviceBserviceC 并行调用(thenCompose())。 在我得到所有结果之后,我想将所有 3 个结果合并并将其返回给某个调用者。 在调用者中,我想在整个过程中等待整个 X 毫秒,以便:

  • 如果我在serviceA 调用正在进行时中断进程:抛出一些异常(所以这是强制性的)
  • 如果我在serviceBserviceC 调用正在进行时中断进程:返回一些默认值(它们是可选的)。 这就是我尝试使用CompletableFuturegetNow(fallback) 方法的原因

请检查下面我的代码 sn-ps,如果我在 serviceBserviceC 调用中使用长时间延迟,我总是以 TimeoutException 结束。 我该怎么做?

public CompletableFuture<Result> getFuture() {
    CompletableFuture<A> resultA = serviceA.call();
    CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
    CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
    return CompletableFuture.allOf(resultB, resultC)
            .thenApply(ignoredVoid -> combine(
                    resultA.join(),
                    resultB.getNow(fallbackB),
                    resultC.getNow(fallbackC));
}

public Result extractFuture(CompletableFuture<Result> future) {
    Result result;
    try {
        result = future.get(timeOut, MILLISECONDS);
    } catch (ExecutionException ex) {
        ...
    } catch (InterruptedException | TimeoutException ex) {
        // I always ends up here...
    }
    return result;
}

【问题讨论】:

    标签: java java-8 completable-future


    【解决方案1】:

    .allOf(resultB, resultC) 返回的 future 仅在 resultBresultC 都完成时才完成,因此,依赖函数 ignoredVoid -&gt; combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC) 仅在 resultBresultC 都完成并提供时才会被评估回退完全没有效果。

    通常不可能对这些函数中的get() 调用做出反应。考虑到在不同的时间和不同的超时时间可以有任意数量的get() 调用,这应该是显而易见的,但是传递给thenApply 的函数只被评估一次。

    getFuture() 中处理消费者指定超时的唯一方法是将其更改为返回一个接收超时的函数:

    interface FutureFunc<R> {
        R get(long time, TimeUnit u) throws ExecutionException;
    }
    public FutureFunc<Result> getFuture() {
        CompletableFuture<A> resultA = serviceA.call();
        CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
        CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
        CompletableFuture<Result> optimistic = CompletableFuture.allOf(resultB, resultC)
            .thenApply(ignoredVoid -> combine(resultA.join(), resultB.join(), resultC.join()));
        return (t,u) -> {
            try {
                return optimistic.get(t, u);
            } catch (InterruptedException | TimeoutException ex) {
                return combine(resultA.join(), resultB.getNow(fallbackB),
                                               resultC.getNow(fallbackC));
            }
        };
    }
    
    public Result extractFuture(FutureFunc<Result> future) {
        Result result;
        try {
            result = future.get(timeOut, MILLISECONDS);
        } catch (ExecutionException ex) {
            ...
        }
        return result;
    }
    

    现在,可以进行具有不同超时的不同调用,只要 B 或 C 尚未完成,就可能产生不同的结果。并不是说combine 方法有一些歧义,这也可能需要一些时间。

    你可以把函数改成一个

    return (t,u) -> {
        try {
            if(resultB.isDone() && resultC.isDone()) return optimistic.get();
            return optimistic.get(t, u);
        } catch (InterruptedException | TimeoutException ex) {
            return combine(resultA.join(), resultB.getNow(fallbackB),
                                           resultC.getNow(fallbackC));
        }
    };
    

    等待可能已经运行的combine 完成。在任何一种情况下,都不能保证在指定时间内交付结果,因为即使使用了 B 和 C 的后备值,也会执行 combine,这可能需要任意时间。

    如果您想要取消类似的行为,即所有结果查询返回相同的结果,即使它是使用第一个查询的后备值计算的,您也可以使用

    public FutureFunc<Result> getFuture() {
        CompletableFuture<A> resultA = serviceA.call();
        CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
        CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
        CompletableFuture<Void> bAndC = CompletableFuture.allOf(resultB, resultC);
        CompletableFuture<Result> result = bAndC
            .thenApply(ignoredVoid -> combine(resultA.join(), resultB.join(),
                                                              resultC.join()));
        return (t,u) -> {
            try {
                bAndC.get(t, u);
            } catch (InterruptedException|TimeoutException ex) {
                resultB.complete(fallbackB);
                resultC.complete(fallbackC);
            }
            try {
                return result.get();
            } catch (InterruptedException ex) {
                throw new ExecutionException(ex);
            }
        };
    }
    

    这样,单个FutureFunc 上的所有查询将始终返回相同的结果,即使由于第一次超时而基于回退值。此变体还始终将combine 的执行排除在超时之外。

    当然,如果根本不打算使用不同的超时,您可以重构 getFuture() 以提前获得所需的超时,例如作为参数。这将大大简化实现,并且可以再次返回未来:

    public CompletableFuture<Result> getFuture(long timeOut, TimeUnit u) {
        CompletableFuture<A> resultA = serviceA.call();
        CompletableFuture<B> resultB = resultA.thenCompose(a -> serviceB.call(a));
        CompletableFuture<C> resultC = resultA.thenCompose(a -> serviceC.call(a));
        ScheduledExecutorService e = Executors.newSingleThreadScheduledExecutor();
        e.schedule(() -> resultB.complete(fallbackB), timeOut, u);
        e.schedule(() -> resultC.complete(fallbackC), timeOut, u);
        CompletableFuture<Void> bAndC = CompletableFuture.allOf(resultB, resultC);
        bAndC.thenRun(e::shutdown);
        return bAndC.thenApply(ignoredVoid ->
                               combine(resultA.join(), resultB.join(), resultC.join()));
    }
    

    【讨论】:

    • 这正是我想要的。它就像一个魅力,感谢您的详细回答!
    猜你喜欢
    • 2014-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-08
    • 2023-03-28
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多