web-dev-qa-db-ja.com

キャンセルする方法Java 8完了可能な未来?

私は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または私の例のバグですか?

19
Lukas

どうやら、それは意図的です。メソッドのJavadoc CompletableFuture :: cancel 状態:

[パラメータ:]mayInterruptIfRunning-この実装では、割り込みが使用されないため、この値はnoの効果があります制御処理。

興味深いことに、メソッド ForkJoinTask :: cancel は、パラメーターmayInterruptIfRunningにほぼ同じ表現を使用します。

私はこの問題について推測しています:

  • interruptionは、sleepwaitなどのブロック操作で使用することを目的としています。またはI/O操作、
  • ただし、CompletableFutureForkJoinTaskも、ブロッキング操作で使用することを目的としていません。

ブロックする代わりに、CompletableFutureは新しいCompletionStageを作成する必要があり、CPUバウンドタスクはフォーク結合モデルの前提条件です。したがって、interruptionをいずれかで使用すると、目的が無効になります。一方で、複雑さが増す可能性があります。意図したとおりに使用する場合は必要ありません。

14
nosid

_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のアップストリームです。

これが舞台裏で起こることです。これは正確ではなく、何が起こっているのかを示す便利なマインドモデルです。

  1. サプライヤ(ステップ1)が実行されています(JVMの共通のForkJoinPool内)。
  2. サプライヤの結果は、complete(...)によって次のCompletableFutureダウンストリームに渡されます。
  3. 結果を受け取ると、そのCompletableFutureは次のステップを呼び出します-前のステップの結果を受け取り、さらに渡されるものを下流のCompletableFuturecomplete(...)
  4. ステップ2の結果を受け取ると、ステップ3 CompletableFutureはコンシューマーSystem.out.println(s)を呼び出します。コンシューマーが終了すると、ダウンストリームCompletableFutureはその値_(Void) null_を受け取ります。

ご覧のとおり、このチェーンの各CompletableFutureは、値が自分のcomplete(...)(またはcompleteExceptionally(...))に渡されるのを待っているダウンストリームに誰がいるかを知る必要があります。ただし、CompletableFutureは、アップストリーム(またはアップストリーム-複数ある場合があります)について何も知る必要はありません。

したがって、ステップ3でcancel()を呼び出しても、ステップ1と2は中止されません、ステップ3からステップ2へのリンクがないためです。

CompletableFutureを使用している場合、ステップは十分に小さいため、いくつかの追加のステップが実行されても害はないと想定されています。

キャンセルをアップストリームに伝播する場合は、次の2つのオプションがあります。

  • これを自分で実装します-専用のCompletableFuturecancelledのような名前)を作成します。これは、すべてのステップ(step.applyToEither(cancelled, Function.identity())のようなもの)の後にチェックされます。
  • RxJava 2、ProjectReactor/Flux、AkkaStreamsなどのリアクティブスタックを使用する
11
Kirill Gamazkov

真のスレッド中断を実現するには、CompletionStageの代替実装が必要です。まさにこの目的に役立つ小さなライブラリをリリースしました--- https://github.com/vsilaev/tascalate-concurrent

2
Valery Silaev

CancellationExceptionは、内部ForkJoinキャンセルルーチンの一部です。 futureの結果を取得すると、例外が発生します。

try { future.get(); }
      catch (Exception e){
          System.out.println(e.toString());            
      }

デバッガーでこれを確認するのにしばらく時間がかかりました。 JavaDocは、何が起こっているのか、何を期待すべきかについてはそれほど明確ではありません。

0
edharned