我有ExecutorService
将计算数据转发到CompletableFuture
:
class DataRetriever {
private final ExecutorService service = ...;
public CompletableFuture<Data> retrieve() {
final CompletableFuture<Data> future = new CompletableFuture<>();
service.execute(() -> {
final Data data = ... fetch data ...
future.complete(data);
});
return future;
}
}
我希望客户/用户能够取消任务:
final DataRetriever retriever = new DataRetriever();
final CompletableFuture<Data> future = retriever().retrieve();
future.cancel(true);
这不起作用,因为这会取消外部CompletableFuture
,但不会取消执行程序服务中计划的内部未来。
以某种方式可能将外部未来的cancel()
传播到内心的未来吗?
答案 0 :(得分:3)
CompletableFuture#cancel
实际上仅用于将CompletableFuture
标记为已取消。它不会通知执行任务停止,因为它与任何此类任务没有任何关系。
此{/ 3>的javadoc提示
mayInterruptIfRunning
- 此值在此实现中无效,因为中断不用于控制处理。
示例中的内部未来是Future
,而不是CompletableFuture
,它确实与执行Runnable
(或{{ 1}})。在内部,因为它知道正在执行任务的Callable
,它可以发送Thread
来尝试阻止它。
一种选择是返回某些元组(例如,某些POJO)的元组,该元组提供对interrupt
和CompletableFuture
返回的Future
的引用。如果需要,您可以使用ExecutorService#submit
至Future
。您必须记住cancel
或cancel
complete
,以便代码的其他部分不会永远被阻止/饿死。
答案 1 :(得分:1)
Pillar提到的另一个解决方案是扩展CompletableFuture
。这是一种与您现有代码非常相似的方法。它还处理异常,这是一个很好的奖励。
class CancelableFuture<T> extends CompletableFuture<T> {
private Future<?> inner;
/**
* Creates a new CancelableFuture which will be completed by calling the
* given {@link Callable} via the provided {@link ExecutorService}.
*/
public CancelableFuture(Callable<T> task, ExecutorService executor) {
this.inner = executor.submit(() -> complete(task));
}
/**
* Completes this future by executing a {@link Callable}. If the call throws
* an exception, the future will complete with that exception. Otherwise,
* the future will complete with the value returned from the callable.
*/
private void complete(Callable<T> callable) {
try {
T result = callable.call();
complete(result);
} catch (Exception e) {
completeExceptionally(e);
}
}
@Override
public boolean cancel(boolean mayInterrupt) {
return inner.cancel(mayInterrupt) && super.cancel(true);
}
}
然后,在DataRetriever
中,您可以执行以下操作:
public CompletableFuture<Data> retrieve() {
return new CancelableFuture<>(() -> {... fetch data ...}, service);
}
答案 2 :(得分:1)
使用我的Tascalate Concurrent库,您的代码可能会被重写为:
class DataRetriever {
private final ExecutorService service = ...;
public Promise<Data> retrieve() {
return CompletableTask.supplyAsync(() -> {
final Data data = ... fetch data ...
return data;
}, service);
}
}
Promise
和CompletableTask
是我图书馆的课程,您可以在my blog
答案 3 :(得分:0)
通过向外部未来添加异常处理程序,您可以将对cancel
的调用传递给内部未来。这是有效的,因为CompletableFuture.cancel
会导致未来使用CancellationException
异常完成。
private final ExecutorService service = ...;
public CompletableFuture<Data> retrieve() {
final CompletableFuture<Data> outer = new CompletableFuture<>();
final Future<?> inner = service.submit(() -> {
...
future.complete(data);
});
outer.exceptionally((error) -> {
if (error instanceof CancellationException) {
inner.cancel(true);
}
return null; // must return something, because 'exceptionally' expects a Function
});
return outer;
}
对outer.exceptionally
的调用会创建一个新的CompletableFuture
,因此它不会影响outer
本身的取消或例外状态。您仍然可以将您喜欢的任何其他CompletionStage
附加到outer
,包括另一个exceptionally
阶段,它将按预期运行。