私はJava 8つの完了可能なfuturesで遊んでいます。次のコードがあります:
CountDownLatch waitLatch = new CountDownLatch(1);
CompletableFuture<?> future = CompletableFuture.runAsync(() -> {
try {
System.out.println("Wait");
waitLatch.await(); //cancel should interrupt
System.out.println("Done");
} catch (InterruptedException e) {
System.out.println("Interrupted");
throw new RuntimeException(e);
}
});
sleep(10); //give it some time to start (ugly, but works)
future.cancel(true);
System.out.println("Cancel called");
assertTrue(future.isCancelled());
assertTrue(future.isDone());
sleep(100); //give it some time to finish
RunAsyncを使用して、ラッチを待機するコードの実行をスケジュールします。次に、中断された例外が内部でスローされることを期待して、futureをキャンセルします。しかし、スレッドはawait呼び出しでブロックされたままであり、futureがキャンセルされても(アサーションパス)、InterruptedExceptionがスローされることはないようです。 ExecutorServiceを使用した同等のコードは期待どおりに機能します。 CompletableFutureまたは私の例のバグですか?
どうやら、それは意図的です。メソッドのJavadoc CompletableFuture :: cancel 状態:
[パラメータ:]mayInterruptIfRunning-この実装では、割り込みが使用されないため、この値はnoの効果があります制御処理。
興味深いことに、メソッド ForkJoinTask :: cancel は、パラメーターmayInterruptIfRunningにほぼ同じ表現を使用します。
私はこの問題について推測しています:
ブロックする代わりに、CompletableFutureは新しいCompletionStageを作成する必要があり、CPUバウンドタスクはフォーク結合モデルの前提条件です。したがって、interruptionをいずれかで使用すると、目的が無効になります。一方で、複雑さが増す可能性があります。意図したとおりに使用する場合は必要ありません。
_CompletableFuture#cancel
_を呼び出すときは、チェーンの下流部分のみを停止します。上流部分、i。 e。最終的にcomplete(...)
またはcompleteExceptionally(...)
を呼び出すものは、結果が不要になったというシグナルを受け取りません。
それらの「上流」と「下流」のものは何ですか?
次のコードについて考えてみましょう。
_CompletableFuture
.supplyAsync(() -> "hello") //1
.thenApply(s -> s + " world!") //2
.thenAccept(s -> System.out.println(s)); //3
_
ここでは、データは上から下に流れます。つまり、サプライヤによって作成され、関数によって変更され、println
によって消費されます。特定のステップの上の部分はアップストリームと呼ばれ、下の部分はダウンストリームと呼ばれます。例:ステップ1と2は、ステップ3のアップストリームです。
これが舞台裏で起こることです。これは正確ではなく、何が起こっているのかを示す便利なマインドモデルです。
ForkJoinPool
内)。complete(...)
によって次のCompletableFuture
ダウンストリームに渡されます。CompletableFuture
は次のステップを呼び出します-前のステップの結果を受け取り、さらに渡されるものを下流のCompletableFuture
のcomplete(...)
。CompletableFuture
はコンシューマーSystem.out.println(s)
を呼び出します。コンシューマーが終了すると、ダウンストリームCompletableFuture
はその値_(Void) null
_を受け取ります。ご覧のとおり、このチェーンの各CompletableFuture
は、値が自分のcomplete(...)
(またはcompleteExceptionally(...)
)に渡されるのを待っているダウンストリームに誰がいるかを知る必要があります。ただし、CompletableFuture
は、アップストリーム(またはアップストリーム-複数ある場合があります)について何も知る必要はありません。
したがって、ステップ3でcancel()
を呼び出しても、ステップ1と2は中止されません、ステップ3からステップ2へのリンクがないためです。
CompletableFuture
を使用している場合、ステップは十分に小さいため、いくつかの追加のステップが実行されても害はないと想定されています。
キャンセルをアップストリームに伝播する場合は、次の2つのオプションがあります。
CompletableFuture
(cancelled
のような名前)を作成します。これは、すべてのステップ(step.applyToEither(cancelled, Function.identity())
のようなもの)の後にチェックされます。真のスレッド中断を実現するには、CompletionStageの代替実装が必要です。まさにこの目的に役立つ小さなライブラリをリリースしました--- https://github.com/vsilaev/tascalate-concurrent
CancellationExceptionは、内部ForkJoinキャンセルルーチンの一部です。 futureの結果を取得すると、例外が発生します。
try { future.get(); }
catch (Exception e){
System.out.println(e.toString());
}
デバッガーでこれを確認するのにしばらく時間がかかりました。 JavaDocは、何が起こっているのか、何を期待すべきかについてはそれほど明確ではありません。