简体   繁体   English

如何中断 CompletableFuture 的底层执行

[英]How to interrupt underlying execution of CompletableFuture

I know that CompletableFuture design does not control its execution with interruptions, but I suppose some of you might have this problem.我知道CompletableFuture设计不会通过中断来控制其执行,但我想你们中的一些人可能会遇到这个问题。 CompletableFuture s are very good way to compose async execution, but given the case when you want the underlying execution to be interrupted or stopped when future is canceled, how do we do that? CompletableFuture是组合异步执行的非常好的方法,但是考虑到您希望在取消 Future 时中断或停止底层执行的情况,我们该怎么做? Or we must just accept that any canceled or manually completed CompletableFuture will not impact the thread working out there to complete it?或者我们必须接受任何取消或手动完成的CompletableFuture不会影响在那里工作以完成它的线程?

That is, in my opinion, obviously a useless work that takes time of executor worker.也就是说,在我看来,这显然是一项需要执行者工作人员时间的无用工作。 I wonder what approach or design might help in this case?我想知道在这种情况下什么方法或设计可能会有所帮助?

UPDATE更新

Here is a simple test for this这是一个简单的测试

public class SimpleTest {

  @Test
  public void testCompletableFuture() throws Exception {
    CompletableFuture<Void> cf = CompletableFuture.runAsync(()->longOperation());

    bearSleep(1);

    //cf.cancel(true);
    cf.complete(null);

    System.out.println("it should die now already");
    bearSleep(7);
  }

  public static void longOperation(){
    System.out.println("started");
    bearSleep(5);
    System.out.println("completed");
  }

  private static void bearSleep(long seconds){
    try {
      TimeUnit.SECONDS.sleep(seconds);
    } catch (InterruptedException e) {
      System.out.println("OMG!!! Interrupt!!!");
    }
  }
}

A CompletableFuture is not related to the asynchronous action that may eventually complete it. CompletableFuture与可能最终完成它的异步操作无关。

Since (unlike FutureTask ) this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion.由于(与FutureTask不同)这个类不能直接控制导致它完成的计算,取消被视为另一种形式的异常完成。 Method cancel has the same effect as completeExceptionally(new CancellationException()) .方法cancelcompleteExceptionally(new CancellationException())具有相同的效果。

There may not even be a separate thread working on completing it (there may even be many threads working on it).这里甚至可能不是一个单独的线程上完成它的工作(甚至有可能是多个线程在它的工作)。 Even if there is, there's no link from a CompletableFuture to any thread that has a reference to it.即使有,也没有从CompletableFuture到任何引用它的线程的链接。

As such, there's nothing you can do through CompletableFuture to interrupt any thread that may be running some task that will complete it.因此,您无法通过CompletableFuture中断任何可能正在运行某些将完成它的任务的线程。 You'll have to write your own logic which tracks any Thread instances which acquire a reference to the CompletableFuture with the intention to complete it.您必须编写自己的逻辑来跟踪任何获取对CompletableFuture的引用并打算完成它的Thread实例。


Here's an example of the type of execution I think you could get away with.这是我认为您可以逃脱的执行类型的示例。

public static void main(String[] args) throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(1);
    CompletableFuture<String> completable = new CompletableFuture<>();
    Future<?> future = service.submit(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (Thread.interrupted()) {
                    return; // remains uncompleted
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    return; // remains uncompleted
                }
            }
            completable.complete("done");
        }
    });

    Thread.sleep(2000);

    // not atomic across the two
    boolean cancelled = future.cancel(true);
    if (cancelled)
        completable.cancel(true); // may not have been cancelled if execution has already completed
    if (completable.isCancelled()) {
        System.out.println("cancelled");
    } else if (completable.isCompletedExceptionally()) {
        System.out.println("exception");
    } else {
        System.out.println("success");
    }
    service.shutdown();
}

This assumes that the task being executed is setup to handle interruptions correctly.这假设正在执行的任务设置为正确处理中断。

What about this?那这个呢?

public static <T> CompletableFuture<T> supplyAsync(final Supplier<T> supplier) {

    final ExecutorService executorService = Executors.newFixedThreadPool(1);

    final CompletableFuture<T> cf = new CompletableFuture<T>() {
        @Override
        public boolean complete(T value) {
            if (isDone()) {
                return false;
            }
            executorService.shutdownNow();
            return super.complete(value);
        }

        @Override
        public boolean completeExceptionally(Throwable ex) {
            if (isDone()) {
                return false;
            }
            executorService.shutdownNow();
            return super.completeExceptionally(ex);
        }
    };

    // submit task
    executorService.submit(() -> {
        try {
            cf.complete(supplier.get());
        } catch (Throwable ex) {
            cf.completeExceptionally(ex);
        }
    });

    return cf;
}

Simple Test:简单测试:

    CompletableFuture<String> cf = supplyAsync(() -> {
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            System.out.println("got interrupted");
            return "got interrupted";
        }
        System.out.println("normal complete");
        return "normal complete";
    });

    cf.complete("manual complete");
    System.out.println(cf.get());

I don't like the idea of having to create an Executor service every time, but maybe you can find a way to reuse the ForkJoinPool.我不喜欢每次都必须创建一个 Executor 服务的想法,但也许你可以找到一种方法来重用 ForkJoinPool。

Please see my answer to related question: Transform Java Future into a CompletableFuture 请参阅我对相关问题的回答: 将Java Future转换为CompletableFuture

In the code mentioned there, the CompletionStage behavior is added to RunnableFuture subclass (used by ExecutorService implementations), so you may interrupt it in the right way. 在那里提到的代码中,CompletionStage行为被添加到RunnableFuture子类(由ExecutorService实现使用),因此您可以以正确的方式中断它。

If you use如果你使用

cf.get();

instead of代替

cf.join();

The thread waiting on the completion can be interrupted.等待完成的线程可以被中断。 This bit me in the a**, so I'm just putting it out there.这让我很不舒服,所以我只是把它放在那里。 You'd then need to propagate this interruption further / use cf.cancel(...) to really finish the execution.然后你需要进一步传播这个中断/使用 cf.cancel(...) 来真正完成执行。

I had similar issue wherein I needed to simulate a InterruptedException.我有类似的问题,我需要模拟一个 InterruptedException。

I mocked the method call that is supposed to return the CompletetableFuture, and I put a spy on return value such that CompletableFuture#get will throw the exception.我嘲笑了应该返回 CompletetableFuture 的方法调用,并且我在返回值上放置了一个间谍,以便CompletableFuture#get将抛出异常。

It worked as I expected, and I was able to test that code handled the exception correctly.它按我的预期工作,并且我能够测试该代码是否正确处理了异常。

        CompletableFuture spiedFuture = spy(CompletableFuture.completedFuture(null));
        when(spiedFuture .get()).thenThrow(new InterruptedException());

        when(servuce.getById(anyString())).thenReturn(spiedFuture );

Here is a ultra-short version to create a Future task that can be cancelled:这是创建可以取消的Future任务的超短版本:

public static <T> Future<T> supplyAsync(Function<Future<T>, T> operation) {
    CompletableFuture<T> future = new CompletableFuture<>();
    return future.completeAsync(() -> operation.apply(future));
}

The CompletableFuture is passed to the operation Function to be able to check the cancel status of the Future : CompletableFuture传递给操作Function以便能够检查Future的取消状态:

Future<Result> future = supplyAsync(task -> {
   while (!task.isCancelled()) {
       // computation
   }
   return result;
});
// later you may cancel
future.cancel(false);
// or retrieve the result
Result result = future.get(5, TimeUnit.SECONDS);

This however does not interrupt the Thread running the operation.但是,这不会中断运行操作的Thread If you also want to be able to interrupt the Thread , then you have to store a reference to it and override Future.cancel(..) to interrupt it.如果您还希望能够中断Thread ,那么您必须存储对它的引用并覆盖Future.cancel(..)以中断它。

public static <T> Future<T> supplyAsync(Function<Future<T>, T> action) {
    return supplyAsync(action, r -> new Thread(r).start());
}

public static <T> Future<T> supplyAsync(Function<Future<T>, T> action, Executor executor) {

    AtomicReference<Thread> interruptThread = new AtomicReference<>();
    CompletableFuture<T> future = new CompletableFuture<>() {

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            if (!interruptThread.compareAndSet(null, Thread.currentThread()) 
                   && mayInterruptIfRunning) {
                interruptThread.get().interrupt();
            }
            return super.cancel(mayInterruptIfRunning);
        }
    };

    executor.execute(() -> {
        if (interruptThread.compareAndSet(null, Thread.currentThread())) try {
            future.complete(action.apply(future));
        } catch (Throwable e) {
            future.completeExceptionally(e);
        }
    });

    return future;
}

The following test checks that the Thread executing our Function got interrupted:以下测试检查执行我们FunctionThread被中断:

@Test
void supplyAsyncWithCancelOnInterrupt() throws Exception {
    Object lock = new Object();
    CountDownLatch done = new CountDownLatch(1);
    CountDownLatch started = new CountDownLatch(1);

    Future<Object> future = supplyAsync(m -> {
        started.countDown();
        synchronized (lock) {
            try {
                lock.wait(); // let's get interrupted
            } catch (InterruptedException e) {
                done.countDown();
            }
        }
        return null;
    });

    assertFalse(future.isCancelled());
    assertFalse(future.isDone());

    assertTrue(started.await(5, TimeUnit.SECONDS));
    assertTrue(future.cancel(true));

    assertTrue(future.isCancelled());
    assertTrue(future.isDone());
    assertThrows(CancellationException.class, () -> future.get());
    assertTrue(done.await(5, TimeUnit.SECONDS));
}

What about?关于什么?

/** @return {@link CompletableFuture} which when cancelled will interrupt the supplier
 */
public static <T> CompletableFuture<T> supplyAsyncInterruptibly(Supplier<T> supplier, Executor executor) {
    return produceInterruptibleCompletableFuture((s) -> CompletableFuture.supplyAsync(s, executor), supplier);
}

// in case we want to do the same for similar methods later
private static <T> CompletableFuture<T> produceInterruptibleCompletableFuture(
        Function<Supplier<T>,CompletableFuture<T>> completableFutureAsyncSupplier, Supplier<T> action) {
    FutureTask<T> task = new FutureTask<>(action::get);
    return addCancellationAction(completableFutureAsyncSupplier.apply(asSupplier(task)), () ->
            task.cancel(true));
}

/** Ensures the specified action is executed if the given {@link CompletableFuture} is cancelled.
 */
public static <T> CompletableFuture<T> addCancellationAction(CompletableFuture<T> completableFuture,
                                                             @NonNull Runnable onCancellationAction) {
    completableFuture.whenComplete((result, throwable) -> {
        if (completableFuture.isCancelled()) {
            onCancellationAction.run();
        }
    });
    return completableFuture;  // return original CompletableFuture
}

/** @return {@link Supplier} wrapper for the given {@link RunnableFuture} which calls {@link RunnableFuture#run()}
 *          followed by {@link RunnableFuture#get()}.
 */
public static <T> Supplier<T> asSupplier(RunnableFuture<T> futureTask) throws CompletionException {
    return () -> {
        try {
            futureTask.run();
            try {
                return futureTask.get();
            } catch (ExecutionException e) {  // unwrap ExecutionExceptions
                final Throwable cause = e.getCause();
                throw (cause != null) ? cause : e;
            }
        } catch (CompletionException e) {
            throw e;
        } catch (Throwable t) {
            throw new CompletionException(t);
        }
    };
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM