取消由ExecutorService控制的CompletableFuture

时间:2016-04-19 19:34:25

标签: java concurrency java.util.concurrent

我有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()传播到内心的未来吗?

4 个答案:

答案 0 :(得分:3)

CompletableFuture#cancel实际上仅用于将CompletableFuture标记为已取消。它不会通知执行任务停止,因为它与任何此类任务没有任何关系。

此{/ 3>的javadoc提示

  

mayInterruptIfRunning - 此值在此实现中无效,因为中断不用于控制处理。

示例中的内部未来是Future,而不是CompletableFuture,它确实与执行Runnable(或{{ 1}})。在内部,因为它知道正在执行任务的Callable,它可以发送Thread来尝试阻止它。

一种选择是返回某些元组(例如,某些POJO)的元组,该元组提供对interruptCompletableFuture返回的Future的引用。如果需要,您可以使用ExecutorService#submitFuture。您必须记住cancelcancel 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);
  }
}

PromiseCompletableTask是我图书馆的课程,您可以在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阶段,它将按预期运行。