繁体   English   中英

具有默认值的Interrupt CompletableFuture

[英]Interrupt CompletableFuture with default value

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

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

请检查下面的代码段,如果我在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;
}

.allOf(resultB, resultC)返回的.allOf(resultB, resultC)仅在resultBresultC都完成resultC完成,因此,相关函数ignoredVoid -> combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC)仅在resultBresultC完成时提供评估,并且提供fallback根本不起作用。

在这些函数中,通常不可能对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 ,而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()));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM