简体   繁体   English

具有默认值的Interrupt CompletableFuture

[英]Interrupt CompletableFuture with default value

Suppose I have 3 services. 假设我有3个服务。 First I call serviceA , it returns a CompletableFuture . 首先我调用serviceA ,它返回一个CompletableFuture After that with the result I call serviceB and serviceC paralelly ( thenCompose() ). 之后,我将结果并行调用serviceBserviceCthenCompose() )。 After I have all result I would like to combine all 3 results and return it to some caller. 得到所有结果后,我想将所有3个结果合并,然后将其返回给某个调用者。 In the caller I would like to wait overall X millseconds to the whole process so that: 在调用方中,我要等待整个过程的整个X毫秒,以便:

  • If I interrupt the process while serviceA call is in progress: throw some exception (so it is mandatory) 如果在进行serviceA调用时中断了该过程:抛出一些异常(因此是强制性的)
  • If I interrupt the process while serviceB and serviceC calls are in progress: return some default value (they are optional). 如果在进行serviceBserviceC调用时中断了该过程,请返回一些默认值(它们是可选的)。 This is the reason I try to use the getNow(fallback) method of the CompletableFuture 这就是我尝试使用CompletableFuturegetNow(fallback)方法的原因

Please check below my code snippets, if I use long delays in serviceB and serviceC calls, I always ends up with a TimeoutException . 请检查下面的代码段,如果我在serviceBserviceC调用中使用了较长的延迟,那么我总是会遇到TimeoutException How can I do this? 我怎样才能做到这一点?

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

The future returned by .allOf(resultB, resultC) is only completed when both, resultB and resultC are completed, therefore, the dependent function ignoredVoid -> combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC) will only get evaluated if resultB and resultC are completed and providing a fallback has no effect at all. .allOf(resultB, resultC)返回的.allOf(resultB, resultC)仅在resultBresultC都完成resultC完成,因此,相关函数ignoredVoid -> combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC)仅在resultBresultC完成时提供评估,并且提供fallback根本不起作用。

It is generally impossible to react on a get() call within these function. 在这些函数中,通常不可能对get()调用做出反应。 Which should be obvious considering that there can be an arbitrary number of get() calls on the future at different times with different timeouts, but the function passed to thenApply is only evaluated once. 考虑到将来在不同的时间有不同的超时可以有任意数量的get()调用,这应该是显而易见的,但是传递给thenApply的函数仅被评估一次。

The only way to handle a consumer specified timeout within getFuture() is to change it to return a function which receives the timeout: 处理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;
}

Now, different calls with different timeouts can be made, with possibly different outcome as long as B or C have not completed yet. 现在,只要B或C尚未完成,就可以进行具有不同超时的不同呼叫,并可能具有不同的结果。 Not that there is some ambiguity regarding the combine method which may also take some time. 并不是说combine方法会产生一些歧义,这可能还需要一些时间。

You could change the function to a 您可以将功能更改为

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

to wait for the completion of a possibly already running combine . 等待可能已经运行的combine In either case, there is no guaranty that the result is delivered within the specified time, as even if the fallback values for B and C are used, there will be an execution of combine that may take an arbitrary amount of time. 无论哪种情况,都无法保证结果在指定的时间内交付,即使使用B和C的后备值,也可能会执行combine ,而combine可能会花费任意时间。

If you want cancellation like behavior, ie that all result queries return the same result, even if it has been calculated using the fallback values due to the first query, you can use instead 如果您想要取消行为,即所有结果查询都返回相同的结果,即使由于第一个查询而使用回退值计算得出了结果,也可以使用

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

With this, all queries on a single FutureFunc will consistently return the same result, even if it is based on fallback values due to the first timeout. 这样,即使单个FutureFunc上的查询基于第一个超时导致的后备值,也将始终返回相同的结果。 This variant also consistently excludes the execution of combine from the timeout. 此变体还始终从超时中排除执行combine

Of course, if different timeouts are not intended at all, you could refactor getFuture() to get the desired timeout in advance, eg as parameter. 当然,如果根本不打算使用不同的超时,则可以重构getFuture()以提前获取所需的超时,例如作为参数。 That would simplify the implementation significantly and it could return a future again: 这将大大简化实施,并可能再次返回未来:

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