変換しようとしていますList<CompletableFuture<X>>
からCompletableFuture<List<T>>
。これは、多くの非同期タスクがあり、それらすべての結果を取得する必要がある場合に非常に便利です。
それらのいずれかが失敗すると、最後の未来は失敗します。これは私が実装した方法です:
public static <T> CompletableFuture<List<T>> sequence2(List<CompletableFuture<T>> com, ExecutorService exec) {
if(com.isEmpty()){
throw new IllegalArgumentException();
}
Stream<? extends CompletableFuture<T>> stream = com.stream();
CompletableFuture<List<T>> init = CompletableFuture.completedFuture(new ArrayList<T>());
return stream.reduce(init, (ls, fut) -> ls.thenComposeAsync(x -> fut.thenApplyAsync(y -> {
x.add(y);
return x;
},exec),exec), (a, b) -> a.thenCombineAsync(b,(ls1,ls2)-> {
ls1.addAll(ls2);
return ls1;
},exec));
}
実行するには:
ExecutorService executorService = Executors.newCachedThreadPool();
Stream<CompletableFuture<Integer>> que = IntStream.range(0,100000).boxed().map(x -> CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep((long) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
return x;
}, executorService));
CompletableFuture<List<Integer>> sequence = sequence2(que.collect(Collectors.toList()), executorService);
それらのいずれかが失敗すると、失敗します。 100万の先物があっても、期待どおりの出力が得られます。私が抱えている問題は、先物が5000を超えていて、そのうちのいずれかが失敗した場合、StackOverflowError
が返されることです。
Java.util.concurrent.CompletableFuture $ ThenCompose.run(CompletableFuture.Java)のJava.util.concurrent.CompletableFuture.internalComplete(CompletableFuture.Java:210)のスレッド「pool-1-thread-2611」Java.lang.StackOverflowErrorの例外:1487)Java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.Java:193)at Java.util.concurrent.CompletableFuture.internalComplete(CompletableFuture.Java:210)at Java.util.concurrent.CompletableFuture $ ThenCompose.run( CompletableFuture.Java:1487)
私はそれを間違っていますか?
注:上記の返されたフューチャーは、フューチャーのいずれかが失敗するとすぐに失敗します。受け入れられた答えもこの点をとるべきです。
CompletableFuture.allOf(...)
を使用します。
_static<T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> com) {
return CompletableFuture.allOf(com.toArray(new CompletableFuture<?>[com.size()]))
.thenApply(v -> com.stream()
.map(CompletableFuture::join)
.collect(toList())
);
}
_
実装に関するいくつかのコメント:
_.thenComposeAsync
_、_.thenApplyAsync
_、および_.thenCombineAsync
_を使用しても、期待どおりに動作しない可能性があります。これらの_...Async
_メソッドは、提供された関数を個別のスレッドで実行します。したがって、あなたの場合、リストに新しいアイテムを追加すると、提供されたエグゼキューターで実行されます。キャッシュされたスレッドエグゼキューターに軽量の操作を詰め込む必要はありません。正当な理由がない限り、thenXXXXAsync
メソッドを使用しないでください。
さらに、reduce
を使用して可変コンテナに蓄積しないでください。ストリームがシーケンシャルの場合は正常に動作する可能性がありますが、ストリームを並列にすると失敗します。可変リダクションを実行するには、代わりに_.collect
_を使用します。
最初の失敗の直後に例外的に計算全体を完了したい場合は、sequence
メソッドで以下を実行します。
_CompletableFuture<List<T>> result = CompletableFuture.allOf(com.toArray(new CompletableFuture<?>[com.size()]))
.thenApply(v -> com.stream()
.map(CompletableFuture::join)
.collect(toList())
);
com.forEach(f -> f.whenComplete((t, ex) -> {
if (ex != null) {
result.completeExceptionally(ex);
}
}));
return result;
_
さらに、最初の失敗で残りの操作をキャンセルする場合は、exec.shutdownNow();
の直後にresult.completeExceptionally(ex);
を追加します。もちろん、これはexec
がこの1つの計算に対してのみ存在することを前提としています。そうでない場合は、ループして残りのFuture
を個別にキャンセルする必要があります。
ミシャが指摘したように なので、…Async
オペレーション。さらに、プログラムロジックを反映しない依存関係をモデル化する複雑な操作チェーンを作成しています。
次に、この再帰的に構成されたジョブを(明示的または例外により)キャンセルすると、再帰的に実行され、StackOverflowError
で失敗する場合があります。それは実装依存です。
すでにMishaによって示されている のように、 allOf
というメソッドがあります。これにより、元の意図をモデル化し、すべてのジョブに依存する1つのジョブを定義できます。あなたのリスト。
ただし、それでも必要ではないことに注意してください。制限のないスレッドプールエグゼキュータを使用しているため、結果を収集する非同期ジョブをリストに単純に投稿できます。とにかく各ジョブの結果を要求することにより、完了を待機することはimpliedです。
ExecutorService executorService = Executors.newCachedThreadPool();
List<CompletableFuture<Integer>> que = IntStream.range(0, 100000)
.mapToObj(x -> CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos((long)(Math.random()*10)));
return x;
}, executorService)).collect(Collectors.toList());
CompletableFuture<List<Integer>> sequence = CompletableFuture.supplyAsync(
() -> que.stream().map(CompletableFuture::join).collect(Collectors.toList()),
executorService);
スレッドの数が制限され、ジョブが追加の非同期ジョブを生成する可能性がある場合、依存する操作を構成するメソッドを使用することが重要です。待機ジョブが最初に完了する必要があるジョブからスレッドを奪うことを避けるためです。
この特定の場合、1つのジョブは、この多数の前提条件ジョブを繰り返し処理し、必要に応じて待機する方が、この多数の依存関係をモデル化し、各ジョブに依存ジョブに完了を通知するよりも効率的です。
SpotifyのCompletableFutures
ライブラリを取得して、 allAsList
メソッドを使用できます。グアバのインスピレーションを受けていると思う Futures.allAsList
メソッド。
public static <T> CompletableFuture<List<T>> allAsList(
List<? extends CompletionStage<? extends T>> stages) {
ライブラリを使用したくない場合の簡単な実装を次に示します。
public <T> CompletableFuture<List<T>> allAsList(final List<CompletableFuture<T>> futures) {
return CompletableFuture.allOf(
futures.toArray(new CompletableFuture[futures.size()])
).thenApply(ignored ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}
@Mishaが受け入れた回答を追加するには、コレクターとしてさらに展開できます。
public static <T> Collector<CompletableFuture<T>, ?, CompletableFuture<List<T>>> sequenceCollector() {
return Collectors.collectingAndThen(Collectors.toList(), com -> sequence(com));
}
次のことができます。
Stream<CompletableFuture<Integer>> stream = Stream.of(
CompletableFuture.completedFuture(1),
CompletableFuture.completedFuture(2),
CompletableFuture.completedFuture(3)
);
CompletableFuture<List<Integer>> ans = stream.collect(sequenceCollector());
CompletableFutureでthenCombineを使用したシーケンス操作の例
public<T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> com){
CompletableFuture<List<T>> identity = CompletableFuture.completedFuture(new ArrayList<T>());
BiFunction<CompletableFuture<List<T>>,CompletableFuture<T>,CompletableFuture<List<T>>> combineToList =
(acc,next) -> acc.thenCombine(next,(a,b) -> { a.add(b); return a;});
BinaryOperator<CompletableFuture<List<T>>> combineLists = (a,b)-> a.thenCombine(b,(l1,l2)-> { l1.addAll(l2); return l1;}) ;
return com.stream()
.reduce(identity,
combineToList,
combineLists);
}
}
サードパーティのライブラリを使用しても構わない場合 cyclops-react (私は著者です)CompletableFutures(およびOptionals、Streamsなど)のユーティリティメソッドのセットがあります
List<CompletableFuture<String>> listOfFutures;
CompletableFuture<ListX<String>> sequence =CompletableFutures.sequence(listOfFutures);
免責事項:これは最初の質問に完全には答えません。 「失敗した場合はすべて失敗する」という部分はありません。ただし、実際のより一般的な質問には答えられません。これは、この質問の複製として閉じられたためです。 Java 8 CompletableFuture.allOf(...)with CollectionまたはList 。だから私はここで答えます:
Java 8のストリームAPIを使用して
List<CompletableFuture<V>>
をCompletableFuture<List<V>>
に変換する方法は?
概要:次を使用します:
private <V> CompletableFuture<List<V>> sequence(List<CompletableFuture<V>> listOfFutures) {
CompletableFuture<List<V>> identity = CompletableFuture.completedFuture(new ArrayList<>());
BiFunction<CompletableFuture<List<V>>, CompletableFuture<V>, CompletableFuture<List<V>>> accumulator = (futureList, futureValue) ->
futureValue.thenCombine(futureList, (value, list) -> {
List<V> newList = new ArrayList<>(list.size() + 1);
newList.addAll(list);
newList.add(value);
return newList;
});
BinaryOperator<CompletableFuture<List<V>>> combiner = (futureList1, futureList2) -> futureList1.thenCombine(futureList2, (list1, list2) -> {
List<V> newList = new ArrayList<>(list1.size() + list2.size());
newList.addAll(list1);
newList.addAll(list2);
return newList;
});
return listOfFutures.stream().reduce(identity, accumulator, combiner);
}
使用例:
List<CompletableFuture<String>> listOfFutures = IntStream.range(0, numThreads)
.mapToObj(i -> loadData(i, executor)).collect(toList());
CompletableFuture<List<String>> futureList = sequence(listOfFutures);
完全な例:
import Java.util.ArrayList;
import Java.util.List;
import Java.util.concurrent.CompletableFuture;
import Java.util.concurrent.Executor;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import Java.util.concurrent.ThreadLocalRandom;
import Java.util.function.BiFunction;
import Java.util.function.BinaryOperator;
import Java.util.stream.IntStream;
import static Java.util.stream.Collectors.toList;
public class ListOfFuturesToFutureOfList {
public static void main(String[] args) {
ListOfFuturesToFutureOfList test = new ListOfFuturesToFutureOfList();
test.load(10);
}
public void load(int numThreads) {
final ExecutorService executor = Executors.newFixedThreadPool(numThreads);
List<CompletableFuture<String>> listOfFutures = IntStream.range(0, numThreads)
.mapToObj(i -> loadData(i, executor)).collect(toList());
CompletableFuture<List<String>> futureList = sequence(listOfFutures);
System.out.println("Future complete before blocking? " + futureList.isDone());
// this will block until all futures are completed
List<String> data = futureList.join();
System.out.println("Loaded data: " + data);
System.out.println("Future complete after blocking? " + futureList.isDone());
executor.shutdown();
}
public CompletableFuture<String> loadData(int dataPoint, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
ThreadLocalRandom rnd = ThreadLocalRandom.current();
System.out.println("Starting to load test data " + dataPoint);
try {
Thread.sleep(500 + rnd.nextInt(1500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Successfully loaded test data " + dataPoint);
return "data " + dataPoint;
}, executor);
}
private <V> CompletableFuture<List<V>> sequence(List<CompletableFuture<V>> listOfFutures) {
CompletableFuture<List<V>> identity = CompletableFuture.completedFuture(new ArrayList<>());
BiFunction<CompletableFuture<List<V>>, CompletableFuture<V>, CompletableFuture<List<V>>> accumulator = (futureList, futureValue) ->
futureValue.thenCombine(futureList, (value, list) -> {
List<V> newList = new ArrayList<>(list.size() + 1);
newList.addAll(list);
newList.add(value);
return newList;
});
BinaryOperator<CompletableFuture<List<V>>> combiner = (futureList1, futureList2) -> futureList1.thenCombine(futureList2, (list1, list2) -> {
List<V> newList = new ArrayList<>(list1.size() + list2.size());
newList.addAll(list1);
newList.addAll(list2);
return newList;
});
return listOfFutures.stream().reduce(identity, accumulator, combiner);
}
}
Javaslang には非常に便利な Future
API があります。また、先物のコレクションから未来のコレクションを作成することもできます。
List<Future<String>> listOfFutures = ...
Future<Seq<String>> futureOfList = Future.sequence(listOfFutures);
Spotify Futuresライブラリに加えて、次の場所にある私のコードを試してみてください: https://github.com/vsilaev/Java-async-await/blob/master/net.tascalate.async.examples/src/main/ Java/net/tascalate/concurrent/CompletionStages.Java (同じパッケージ内の他のクラスへの依存関係があります)
「Mのうち少なくともN」のCompletionStage-sを、許容されるエラーの量をポリシーとして返すロジックを実装します。すべて/すべてのケースに便利なメソッドに加えて、残りの先物のキャンセルポリシーに加えて、CompletableFuture(具象クラス)ではなくCompletionStage-s(インターフェイス)を扱うコードがあります。