如何中断CompletableFuture的底层执行

时间:2015-03-12 15:27:32

标签: java concurrency completable-future

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

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

更新

这是一个简单的测试

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

7 个答案:

答案 0 :(得分:14)

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

  

因为(与FutureTask不同)这个类没有直接控制   导致它完成的计算,取消被视为   只是另一种特殊完成形式。方法cancel有   效果与completeExceptionally(new CancellationException())相同。

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

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


这是我认为你可以逃脱的执行类型的一个例子。

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

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

答案 1 :(得分: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。

答案 2 :(得分:1)

请参阅我对相关问题的回答: Transform Java Future into a CompletableFuture

在那里提到的代码中,CompletionStage行为被添加到RunnableFuture子类(由ExecutorService实现使用),因此您可以以正确的方式中断它。

答案 3 :(得分:1)

我遇到了类似的问题,需要模拟 InterruptedException。

我模拟了应该返回 CompletetableFuture 的方法调用,并且我对返回值进行了监视,这样 CompletableFuture#get 将抛出异常。

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

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

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

答案 4 :(得分:1)

以下是创建可取消的 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;
}

以下测试检查执行我们的 ThreadFunction 是否被中断:

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

答案 5 :(得分:0)

那怎么办?

startTimeBox = browser.find_element_by_name('TimeStart')
startTimeBox.get_attribute('outerHTML')
Out[187]: '<input name="TimeStart" id="TimeStart" title="Please enter start time" size="10" maxlength="12" value="00:00:00" type="time">'

startDateBox = browser.find_element_by_name('DateStart')
startDateBox.get_attribute('outerHTML')
Out[185]: '<input name="DateStart" id="DateStart" title="Please enter start date" size="10" maxlength="11" value="09/11/2018" style="" type="date">'

答案 6 :(得分:0)

如果您使用

cf.get();

代替

cf.join();

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