【问题标题】:How to interrupt underlying execution of CompletableFuture如何中断 CompletableFuture 的底层执行
【发布时间】:2015-03-12 15:27:31
【问题描述】:

我知道CompletableFuture 设计不会通过中断来控制其执行,但我想你们中的一些人可能会遇到这个问题。 CompletableFutures 是编写异步执行的非常好的方法,但是如果您希望在取消未来时中断或停止底层执行,我们该怎么做呢?或者我们必须接受任何取消或手动完成的CompletableFuture 不会影响在那里完成它的线程?

在我看来,这显然是一项无用的工作,需要 executor worker 的时间。我想知道在这种情况下哪种方法或设计可能会有所帮助?

更新

这是一个简单的测试

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!!!");
    }
  }
}

【问题讨论】:

  • 我想知道我们是否可以实现类似它的静态方法 supplyAsync 但有一些额外的逻辑来检查它的完成或取消是否会中断它正在执行的任务线程...
  • 请查看我对相关问题的回答:stackoverflow.com/questions/23301598/… 在那里提到的代码中,CompletionStage 行为被添加到 RunnableFuture 子类(由 ExecutorService 实现使用),因此您可以以正确的方式中断它。

标签: java concurrency completable-future


【解决方案1】:

CompletableFuture 与可能最终完成它的异步操作无关。

因为(不像FutureTask)这个类不能直接控制 导致它完成的计算,取消被视为 只是另一种形式的异常完成。方法cancel 具有 效果和completeExceptionally(new CancellationException())一样。

甚至可能没有个单独的线程来完成它(甚至可能有许多个线程在处理它)。即使有,也没有从 CompletableFuture 到任何引用它的线程的链接。

因此,您无法通过CompletableFuture 执行任何操作来中断任何可能正在运行某个任务以完成它的线程。您必须编写自己的逻辑来跟踪任何获得对 CompletableFuture 的引用以完成它的 Thread 实例。


这是我认为您可以逃脱的执行类型的示例。

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();
}

这假定正在执行的任务已设置为正确处理中断。

【讨论】:

  • @Vach 您必须保证唯一可以访问CompletableFutureThreads 是通过您的自定义工厂方法的那些。这不太可行,imo。这里重要的是CF与Thread中的任何一个都没有关系。如果您需要取消某个线程的工作,您应该通过ExecutorService 提交工作,检索Future 并取消它。提交的RunnableCallable 可以引用CF。您不会取消 CF,而是通过Future 取消任务。
  • @Vach 我会写一个例子来说明我的意思。给我一些。
  • @Vach 更新了一个非常简单的示例,其中只有一个线程执行可以设置CompletableFuture 的工作。
  • @Vach 所以你的确切目标是无法实现的,因为CompletableFuture 没有底层线程。您在线程和CompletableFuture 之间建立的关联是您自己的。 CompletableFuture 不是这样设计的。 FutureCompletableFuture 最接近的就是我上面介绍的内容。取消Future。如果成功,取消关联的CompletableFuture。显然,您可能可以通过编写自己的CompletableFuture 来获得您想要的行为,但这并不是一件容易的事。
  • @SotiriosDelimanolis 感谢您的解释。在您的示例中,您使用 Thread.sleep() 已经检查中断,因此使用 Thread.sleep() 可能不是解释您的观点的更好方法(尽管解释是正确的)。例如,评论if (Thread.interrupted()) {..},程序同样会被中断。我认为代码应该改为doIninterrumpibleComputation(),而不是Thread.sleep()
【解决方案2】:

这个怎么样?

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;
}

简单测试:

    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());

我不喜欢每次都必须创建 Executor 服务的想法,但也许您可以找到一种方法来重用 ForkJoinPool。

【讨论】:

【解决方案3】:

如果你使用

cf.get();

而不是

cf.join();

等待完成的线程可以被中断。这让我陷入了**,所以我只是把它放在那里。然后,您需要进一步传播此中断/使用 cf.cancel(...) 来真正完成执行。

【讨论】:

    【解决方案4】:

    我有类似的问题,我需要模拟 InterruptedException。

    我模拟了应该返回 CompletetableFuture 的方法调用,并在返回值上放置了一个间谍,这样CompletableFuture#get 将抛出异常。

    它按我的预期工作,并且我能够测试该代码正确处理了异常。

            CompletableFuture spiedFuture = spy(CompletableFuture.completedFuture(null));
            when(spiedFuture .get()).thenThrow(new InterruptedException());
    
            when(servuce.getById(anyString())).thenReturn(spiedFuture );
    

    【讨论】:

      【解决方案5】:

      这里是创建Future可以取消的任务的超短版:

      public static <T> Future<T> supplyAsync(Function<Future<T>, T> operation) {
          CompletableFuture<T> future = new CompletableFuture<>();
          return future.completeAsync(() -> operation.apply(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);
      

      但这不会中断运行该操作的Thread。如果您还希望能够中断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;
      }
      

      以下测试检查执行我们的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));
      }
      

      【讨论】:

        【解决方案6】:

        怎么样?

        /** @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);
                }
            };
        }
        

        【讨论】:

        • 如何使用上面的代码 sn-p 停止底层的ExecutorService
        猜你喜欢
        • 2023-03-28
        • 1970-01-01
        • 2020-12-27
        • 1970-01-01
        • 2019-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多