[英]How to interrupt underlying execution of CompletableFuture
我知道CompletableFuture
設計不會通過中斷來控制其執行,但我想你們中的一些人可能會遇到這個問題。 CompletableFuture
是組合異步執行的非常好的方法,但是考慮到您希望在取消 Future 時中斷或停止底層執行的情況,我們該怎么做? 或者我們必須接受任何取消或手動完成的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!!!");
}
}
}
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();
}
這假設正在執行的任務設置為正確處理中斷。
那這個呢?
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。
請參閱我對相關問題的回答: 將Java Future轉換為CompletableFuture
在那里提到的代碼中,CompletionStage行為被添加到RunnableFuture子類(由ExecutorService實現使用),因此您可以以正確的方式中斷它。
如果你使用
cf.get();
代替
cf.join();
等待完成的線程可以被中斷。 這讓我很不舒服,所以我只是把它放在那里。 然后你需要進一步傳播這個中斷/使用 cf.cancel(...) 來真正完成執行。
我有類似的問題,我需要模擬一個 InterruptedException。
我嘲笑了應該返回 CompletetableFuture 的方法調用,並且我在返回值上放置了一個間諜,以便CompletableFuture#get
將拋出異常。
它按我的預期工作,並且我能夠測試該代碼是否正確處理了異常。
CompletableFuture spiedFuture = spy(CompletableFuture.completedFuture(null));
when(spiedFuture .get()).thenThrow(new InterruptedException());
when(servuce.getById(anyString())).thenReturn(spiedFuture );
這是創建可以取消的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;
}
以下測試檢查執行我們Function
的Thread
被中斷:
@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));
}
關於什么?
/** @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.