web-dev-qa-db-ja.com

CompletableFuture:最初に戻るのを待っていますか?

いくつかのCompletableFuturesがあり、それらを並行して実行して、最初に通常を返すのを待っています。

CompletableFuture.anyOf最初に戻るのを待ちますが、これは通常または例外的にを返します。例外を無視したい。

List<CompletableFuture<?>> futures = names.stream().map(
  (String name) ->
    CompletableFuture.supplyAsync(
      () ->
        // this calling may throw exceptions.
        new Task(name).run()
    )
).collect(Collectors.toList());
//FIXME Can not ignore exceptionally returned takes.
Future any = CompletableFuture.anyOf(futures.toArray(new CompletableFuture<?>[]{}));
try {
    logger.info(any.get().toString());
} catch (Exception e) {
    e.printStackTrace();
}
22
Jason

次のヘルパーメソッドを使用できます。

public static <T>
    CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {

    CompletableFuture<T> f=new CompletableFuture<>();
    Consumer<T> complete=f::complete;
    l.forEach(s -> s.thenAccept(complete));
    return f;
}

これを使用して、以前の例外を無視し、最初に提供された値を返すことを示すことができます。

List<CompletableFuture<String>> futures = Arrays.asList(
    CompletableFuture.supplyAsync(
        () -> { throw new RuntimeException("failing immediately"); }
    ),
    CompletableFuture.supplyAsync(
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
          return "with 5s delay";
        }),
    CompletableFuture.supplyAsync(
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
          return "with 10s delay";
        })
);
CompletableFuture<String> c = anyOf(futures);
logger.info(c.join());

このソリューションの欠点の1つは、all先物であれば、決して完了しないことです。例外的に完了します。計算が成功した場合に最初の値を提供しますが、計算がまったく失敗した場合に例外的に失敗するソリューションは、少し複雑です。

public static <T>
    CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {

    CompletableFuture<T> f=new CompletableFuture<>();
    Consumer<T> complete=f::complete;
    CompletableFuture.allOf(
        l.stream().map(s -> s.thenAccept(complete)).toArray(CompletableFuture<?>[]::new)
    ).exceptionally(ex -> { f.completeExceptionally(ex); return null; });
    return f;
}

これは、allOfの例外ハンドラーがすべてのフューチャーが完了した後にのみ(例外的にかどうかにかかわらず)呼び出され、フューチャーは1回だけ完了することができるという事実(obtrude…は別として)。例外ハンドラーが実行されると、futureを完了するための結果が存在する場合は、それを完了しようとする試みがすべて行われたため、以前に正常に完了しなかった場合にのみ、例外を完了する試みは成功します。

これは最初のソリューションとまったく同じ方法で使用でき、すべての計算が失敗した場合にのみ異なる動作を示します。例:

List<CompletableFuture<String>> futures = Arrays.asList(
    CompletableFuture.supplyAsync(
        () -> { throw new RuntimeException("failing immediately"); }
    ),
    CompletableFuture.supplyAsync(
        // delayed to demonstrate that the solution will wait for all completions
        // to ensure it doesn't miss a possible successful computation
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
            throw new RuntimeException("failing later"); }
    )
);
CompletableFuture<String> c = anyOf(futures);
try { logger.info(c.join()); }
catch(CompletionException ex) { logger.severe(ex.toString()); }

上記の例では遅延が使用されており、成功しない場合にソリューションがすべての完了を待機することを示していますが、 ideoneのこの例 は、後で成功すると結果が成功する方法を示します。結果のIdeonesキャッシングにより、遅延に気付かない場合があることに注意してください。

すべての先物が失敗した場合、どの例外が報告されるかについての保証はありません。エラーの場合はすべての完了を待機するため、最終結果に到達する可能性があります。

9
Holger

それを考慮して:

  1. Java=の哲学の基礎の1つは、不適切なプログラミングを防止または阻止することです。

    (それがどの程度成功したかは、別の議論の主題です。これは、これが言語の主な目的の1つであることに疑いの余地がないことです。)

  2. 例外を無視することは非常に悪い習慣です。

    例外は常にrethrow上のレイヤー、またはhandled、または少なくともreported。のいずれかである必要があります。具体的には、例外は- 静かに飲み込まないでください。

  3. エラーはできるだけ早く報告する必要があります。

    たとえば、fail fastイテレータを提供してConcurrentModificationException反復中にコレクションが変更された場合。

  4. 例外的に完了したCompletableFutureを無視するということは、a)できるだけ早い段階でエラーを報告していないこと、およびb)あなたが可能性が高いまったく報告しないことを計画しています。

  5. 最初の非例外的な完了を単に待つことができず、代わりに例外的な完了に悩まされる必要がない場合、例外的に完了したアイテムをリストから常に削除できるため、大きな負担はかかりません(同時に、失敗を報告し、right?)待機を繰り返します。

したがって、求められている機能が意図的にJavaから欠落しているとしても、私は驚かないでしょう。そして、それは正しくありません。

(申し訳ありませんが、Sotirios、正規の回答はありません。)

4
Mike Nakis

まあ、それはフレームワークでサポートされるべき方法です。まず、私は CompletionStage.applyToEither が同様のことを行うと思いましたが、実際はそうではありません。だから私はこの解決策を思いつきました:

public static <U> CompletionStage<U> firstCompleted(Collection<CompletionStage<U>> stages) {
  final int count = stages.size();
  if (count <= 0) {
    throw new IllegalArgumentException("stages must not be empty");
  }
  final AtomicInteger settled = new AtomicInteger();
  final CompletableFuture<U> future = new CompletableFuture<U>();
  BiConsumer<U, Throwable> consumer = (val, exc) -> {
    if (exc == null) {
      future.complete(val);
    } else {
      if (settled.incrementAndGet() >= count) {
        // Complete with the last exception. You can aggregate all the exceptions if you wish.
        future.completeExceptionally(exc);
      }
    }
  };
  for (CompletionStage<U> item : stages) {
    item.whenComplete(consumer);
  }
  return future;
}

実際の動作を確認するために、以下にいくつかの使用方法を示します。

import Java.util.ArrayList;
import Java.util.Collection;
import Java.util.List;
import Java.util.concurrent.CompletableFuture;
import Java.util.concurrent.CompletionStage;
import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.Executors;
import Java.util.concurrent.ScheduledExecutorService;
import Java.util.concurrent.TimeUnit;
import Java.util.concurrent.atomic.AtomicInteger;
import Java.util.function.BiConsumer;

public class Main {
  public static <U> CompletionStage<U> firstCompleted(Collection<CompletionStage<U>> stages) {
    final int count = stages.size();
    if (count <= 0) {
      throw new IllegalArgumentException("stages must not be empty");
    }
    final AtomicInteger settled = new AtomicInteger();
    final CompletableFuture<U> future = new CompletableFuture<U>();
    BiConsumer<U, Throwable> consumer = (val, exc) -> {
      if (exc == null) {
        future.complete(val);
      } else {
        if (settled.incrementAndGet() >= count) {
          // Complete with the last exception. You can aggregate all the exceptions if you wish.
          future.completeExceptionally(exc);
        }
      }
    };
    for (CompletionStage<U> item : stages) {
      item.whenComplete(consumer);
    }
    return future;
  }

  private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

  public static <U> CompletionStage<U> delayed(final U value, long delay) {
    CompletableFuture<U> future = new CompletableFuture<U>();
    worker.schedule(() -> {
      future.complete(value);
    }, delay, TimeUnit.MILLISECONDS);
    return future;
  }
  public static <U> CompletionStage<U> delayedExceptionally(final Throwable value, long delay) {
    CompletableFuture<U> future = new CompletableFuture<U>();
    worker.schedule(() -> {
      future.completeExceptionally(value);
    }, delay, TimeUnit.MILLISECONDS);
    return future;
  }

  public static void main(String[] args) throws InterruptedException, ExecutionException {
    System.out.println("Started...");

    /*
    // Looks like applyToEither doesn't work as expected
    CompletableFuture<Integer> a = CompletableFuture.completedFuture(99);
    CompletableFuture<Integer> b = Main.<Integer>completedExceptionally(new Exception("Exc")).toCompletableFuture();
    System.out.println(b.applyToEither(a, x -> x).get()); // throws Exc
    */

    try {
      List<CompletionStage<Integer>> futures = new ArrayList<>();
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #1"), 100));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #2"), 200));
      futures.add(delayed(1, 1000));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #4"), 400));
      futures.add(delayed(2, 500));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #5"), 600));
      Integer value = firstCompleted(futures).toCompletableFuture().get();
      System.out.println("Completed normally: " + value);
    } catch (Exception ex) {
      System.out.println("Completed exceptionally");
      ex.printStackTrace();
    }

    try {
      List<CompletionStage<Integer>> futures = new ArrayList<>();
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception B#1"), 400));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception B#2"), 200));
      Integer value = firstCompleted(futures).toCompletableFuture().get();
      System.out.println("Completed normally: " + value);
    } catch (Exception ex) {
      System.out.println("Completed exceptionally");
      ex.printStackTrace();
    }

    System.out.println("End...");
  }

}
2
Tamas Hegedus