[英]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()
). 之后,我将结果并行调用
serviceB
和serviceC
( thenCompose()
)。 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毫秒,以便:
serviceA
call is in progress: throw some exception (so it is mandatory) serviceA
调用时中断了该过程:抛出一些异常(因此是强制性的) serviceB
and serviceC
calls are in progress: return some default value (they are optional). serviceB
和serviceC
调用时中断了该过程,请返回一些默认值(它们是可选的)。 This is the reason I try to use the getNow(fallback)
method of the CompletableFuture
CompletableFuture
的getNow(fallback)
方法的原因 Please check below my code snippets, if I use long delays in serviceB
and serviceC
calls, I always ends up with a TimeoutException
. 请检查下面的代码段,如果我在
serviceB
和serviceC
调用中使用了较长的延迟,那么我总是会遇到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)
仅在resultB
和resultC
都完成resultC
完成,因此,相关函数ignoredVoid -> combine(resultA.join(), resultB.getNow(fallbackB), resultC.getNow(fallbackC)
仅在resultB
和resultC
完成时提供评估,并且提供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.