web-dev-qa-db-ja.com

List <Future> to Future <List>シーケンス

変換しようとしています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)

私はそれを間違っていますか?

注:上記の返されたフューチャーは、フューチャーのいずれかが失敗するとすぐに失敗します。受け入れられた答えもこの点をとるべきです。

62
Jatin

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を個別にキャンセルする必要があります。

78
Misha

ミシャが指摘したように なので、…Async オペレーション。さらに、プログラムロジックを反映しない依存関係をモデル化する複雑な操作チェーンを作成しています。

  • リストの最初と2番目のジョブに依存するジョブxを作成します
  • ジョブxとリストの3番目のジョブに依存するジョブx + 1を作成します
  • ジョブx + 1とリストの4番目のジョブに依存するジョブx + 2を作成します
  • ジョブx + 4999とリストの最後のジョブに依存するジョブx + 5000を作成します

次に、この再帰的に構成されたジョブを(明示的または例外により)キャンセルすると、再帰的に実行され、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つのジョブは、この多数の前提条件ジョブを繰り返し処理し、必要に応じて待機する方が、この多数の依存関係をモデル化し、各ジョブに依存ジョブに完了を通知するよりも効率的です。

11
Holger

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())
    );
}
7
oskansavli

@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());
4
Jatin

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);
2
John McClean

免責事項:これは最初の質問に完全には答えません。 「失敗した場合はすべて失敗する」という部分はありません。ただし、実際のより一般的な質問には答えられません。これは、この質問の複製として閉じられたためです。 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);
    }

}
1
Kai Stapel

Javaslang には非常に便利な Future AP​​I があります。また、先物のコレクションから未来のコレクションを作成することもできます。

List<Future<String>> listOfFutures = ... 
Future<Seq<String>> futureOfList = Future.sequence(listOfFutures);

http://static.javadoc.io/io.javaslang/javaslang/2.0.5/javaslang/concurrent/Future.html#sequence-Java.lang.Iterable- を参照してください

0
Mathias Dpunkt

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(インターフェイス)を扱うコードがあります。

0
Valery Silaev