いくつかのCompletableFuture
sがあり、それらを並行して実行して、最初に通常を返すのを待っています。
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();
}
次のヘルパーメソッドを使用できます。
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キャッシングにより、遅延に気付かない場合があることに注意してください。
すべての先物が失敗した場合、どの例外が報告されるかについての保証はありません。エラーの場合はすべての完了を待機するため、最終結果に到達する可能性があります。
それを考慮して:
Java=の哲学の基礎の1つは、不適切なプログラミングを防止または阻止することです。
(それがどの程度成功したかは、別の議論の主題です。これは、これが言語の主な目的の1つであることに疑いの余地がないことです。)
例外を無視することは非常に悪い習慣です。
例外は常にrethrow上のレイヤー、またはhandled、または少なくともreported。のいずれかである必要があります。具体的には、例外は- 静かに飲み込まないでください。
エラーはできるだけ早く報告する必要があります。
たとえば、fail fastイテレータを提供してConcurrentModificationException反復中にコレクションが変更された場合。
例外的に完了したCompletableFuture
を無視するということは、a)できるだけ早い段階でエラーを報告していないこと、およびb)あなたが可能性が高いまったく報告しないことを計画しています。
最初の非例外的な完了を単に待つことができず、代わりに例外的な完了に悩まされる必要がない場合、例外的に完了したアイテムをリストから常に削除できるため、大きな負担はかかりません(同時に、失敗を報告し、right?)待機を繰り返します。
したがって、求められている機能が意図的にJavaから欠落しているとしても、私は驚かないでしょう。そして、それは正しくありません。
(申し訳ありませんが、Sotirios、正規の回答はありません。)
まあ、それはフレームワークでサポートされるべき方法です。まず、私は 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...");
}
}