web-dev-qa-db-ja.com

CompletableFutureの基になる実行を中断する方法

私はCompletableFuture設計が割り込みでその実行を制御しないことを知っていますが、あなたの一部はこの問題を抱えていると思います。 CompletableFuturesは非同期実行を構成するための非常に優れた方法ですが、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!!!");
    }
  }
}
24
vach

CompletableFuture は、最終的に完了する可能性のある非同期アクションとは関係ありません。

FutureTaskとは異なり)このクラスは、それを完了させる計算を直接制御しないため、キャンセルは例外的な完了の別の形式として扱われます。メソッドcancelcompleteExceptionally(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();
}

これは、実行中のタスクが割り込みを正しく処理するように設定されていることを前提としています。

15

これはどうですか?

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を再利用する方法を見つけることができるかもしれません。

3
Ruben

関連する質問への私の回答をご覧ください: Transform Java Future to a CompletableFuture

そこで言及されているコードでは、CompletionStage動作がRunnableFutureサブクラス(ExecutorService実装によって使用される)に追加されているため、正しい方法で中断することができます。

1
Valery Silaev

どう?

/** @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);
        }
    };
}
0
user6519354

使用する場合

cf.get();

の代わりに

cf.join();

完了を待機しているスレッドを中断できます。これは私をa **に噛んだので、私はそれをそこに置いています。次に、この割り込みをさらに伝播する必要があります。実際に実行を完了するには、cf.cancel(...)を使用します。

0
jontejj