簡體   English   中英

取消由ExecutorService控制的CompletableFuture

[英]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),該元組同時提供對您的CompletableFutureExecutorService#submit返回的Future的引用。 如果需要,可以使用Future cancel 您必須記住要cancelcomplete您的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);
  }
}

PromiseCompletableTask是我庫中的類,您可以在我的博客中閱讀更多內容

通過在外部環境中添加異常處理程序,您可以將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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM