[英]Cancellation of CompletableFuture controlled by ExecutorService
我有一個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
,但不會取消執行程序服務中計划的內部CompletableFuture
。
是否有可能在外部期貨上傳播cancel()
到內部期貨?
CompletableFuture#cancel
實際上僅用於將CompletableFuture
標記為已取消的目的。 它沒有通知停止執行的任務,因為它與任何此類任務都沒有關系。
javadoc暗示了這一點
mayInterruptIfRunning
此值在此實現中無效,因為不使用中斷來控制處理。
在你的例子內未來是Future
,而不是一個CompletableFuture
,它也有一個關系到執行Runnable
(或Callable
)。 在內部,由於它知道任務在哪個Thread
上執行,因此可以向其發送interrupt
以嘗試將其停止。
一種選擇是返回某種元組(例如某些POJO),該元組同時提供對您的CompletableFuture
和ExecutorService#submit
返回的Future
的引用。 如果需要,可以使用Future
cancel
。 您必須記住要cancel
或complete
您的CompletableFuture
以使代碼的其他部分不會永遠被阻塞/飢餓。
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);
}
使用我的Tascalate並發庫,您的代碼可以按以下方式重寫:
class DataRetriever {
private final ExecutorService service = ...;
public Promise<Data> retrieve() {
return CompletableTask.supplyAsync(() -> {
final Data data = ... fetch data ...
return data;
}, service);
}
}
Promise
和CompletableTask
是我庫中的類,您可以在我的博客中閱讀更多內容
通過在外部環境中添加異常處理程序,您可以將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
階段,它將按預期運行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.