私はCompletableFuture
設計が割り込みでその実行を制御しないことを知っていますが、あなたの一部はこの問題を抱えていると思います。 CompletableFuture
sは非同期実行を構成するための非常に優れた方法ですが、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())
と同じ効果があります。
beそれを完了するために作業している別のスレッドが存在しない場合もあります(many作業中のスレッド)。存在する場合でも、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();
}
これは、実行中のタスクが割り込みを正しく処理するように設定されていることを前提としています。
これはどうですか?
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を再利用する方法を見つけることができるかもしれません。
関連する質問への私の回答をご覧ください: Transform Java Future to a CompletableFuture
そこで言及されているコードでは、CompletionStage動作がRunnableFutureサブクラス(ExecutorService実装によって使用される)に追加されているため、正しい方法で中断することができます。
どう?
/** @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);
}
};
}
使用する場合
cf.get();
の代わりに
cf.join();
完了を待機しているスレッドを中断できます。これは私をa **に噛んだので、私はそれをそこに置いています。次に、この割り込みをさらに伝播する必要があります。実際に実行を完了するには、cf.cancel(...)を使用します。