web-dev-qa-db-ja.com

3つ以上のCompletionStageを組み合わせる方法は?

2つのCompletionStagesがある場合、それらをthenCombineメソッドと組み合わせることができます。

CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<Combined> combinedCompletionStage =
    aCompletionStage.thenCombine(bCompletionStage, (aData, bData) -> combine(aData, bData));

3つ以上のCompletionStageがある場合、thenCombineメソッドのチェーンを作成できますが、結果を渡すために一時オブジェクトを使用する必要があります。たとえば、以下はorg.Apache.commons.lang3.TupleパッケージのPairTripleを使用したソリューションです。

CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<C> cCompletionStage = getC();
CompletionStage<D> dCompletionStage = getD();

CompletionStage<Combined> combinedDataCompletionStage =
        aCompletionStage.thenCombine(bCompletionStage, (Pair::of))
                .thenCombine(cCompletionStage, (ab, c) ->
                        Triple.of(ab.getLeft(), ab.getRight(), c))
                .thenCombine(dCompletionStage, (abc, d) ->
                        combine(abc.getLeft(), abc.getMiddle(), abc.getRight(), d));

複数のCompletionStageの結果を組み合わせるより良い方法はありますか?

19

ステージ数の増加に合わせて適切にスケーリングする複数のステージを組み合わせる唯一の方法は、CompletableFutureを使用することです。 CompletionStagesがCompletableFuturesではない場合でも、.toCompletableFuture()を使用して変換できます。

CompletableFuture<A> aCompletionStage = getA().toCompletableFuture();
CompletableFuture<B> bCompletionStage = getB().toCompletableFuture();
CompletableFuture<C> cCompletionStage = getC().toCompletableFuture();
CompletableFuture<D> dCompletionStage = getD().toCompletableFuture();

CompletionStage<Combined> combinedDataCompletionStage = CompletableFuture.allOf(
    aCompletionStage, bCompletionStage, cCompletionStage, dCompletionStage)
    .thenApply(ignoredVoid -> combine(
        aCompletionStage.join(), bCompletionStage.join(),
        cCompletionStage.join(), dCompletionStage.join()) );

これには、thenCombineを介して2つのステージを組み合わせるよりも多くのボイラープレートが含まれていますが、ボイラープレートにステージを追加しても、ボイラープレートは成長しません。


元のthenCombineのアプローチでも、Tripleは必要ありません。Pairで十分です。

CompletionStage<Combined> combinedDataCompletionStage =
    aCompletionStage.thenCombine(bCompletionStage, (Pair::of)).thenCombine(
        cCompletionStage.thenCombine(dCompletionStage, Pair::of),
        (ab, cd) -> combine(ab.getLeft(), ab.getRight(), cd.getLeft(), cd.getRight()));

それでも、より多くのステージを組み合わせる場合は、適切にスケーリングされません。


(複雑さに関する)中間のソリューションは次のようになります。

CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
    a -> bCompletionStage.thenCompose(b -> cCompletionStage.thenCompose(
        c -> dCompletionStage.thenApply(d -> combine(a, b, c, d)))));

これは構造が単純ですが、ステージを増やすと拡張性が低下します。

35
Holger

ホルガーの3番目の答え は少し短くできます:

CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
    a -> bCompletionStage.thenCompose(
        b -> cCompletionStage.thenCombine(dCompletionStage,
            (c, d) -> combine(a, b, c, d))));
3

中間オブジェクトを使用する必要があると思いますが、PairTupleを使用する代わりに、独自のオブジェクトを使用してください

public R method() {
    CompletableFuture<A> aFuture = getAFuture();
    CompletableFuture<B> bFuture = getBFuture();
    CompletableFuture<C> cFuture = getCFuture();
    CompletableFuture<D> dFuture = getDFuture();

    return CompletableFuture.completedFuture(new WellNamedResultHolder())
            .thenCombineAsync(aFuture, WellNamedResultHolder::withAResult)
            .thenCombineAsync(bFuture, WellNamedResultHolder::withBResult)
            .thenCombineAsync(cFuture, WellNamedResultHolder::withCResult)
            .thenCombineAsync(dFuture, WellNamedResultHolder::withDResult)
            .thenApplyAsync(this::combineAllTheResults);
}

private static class WellNamedResultHolder {
    private A aResult;
    private B bResult;
    private C cResult;
    private D dResult;

    // Getters

    public WellNamedResultHolder withAResult(final A aResult) {
        this.aResult = aResult;
        return this;
    }

    public WellNamedResultHolder withBResult(final B bResult) {
        this.bResult = bResult;
        return this;
    }

    public WellNamedResultHolder withCResult(final C cResult) {
        this.cResult = cResult;
        return this;
    }

    public WellNamedResultHolder withDResult(final D dResult) {
        this.dResult = dResult;
        return this;
    }
}

結果ホルダーの実際の形式は明らかにユーザーのニーズに合わせて変更できるため、柔軟性が向上します。また、これらの先物が完了すると何が起こるかを担当することになります。ボイラープレートはもっとありますが、何が起こっているかをより詳しく説明するコードを取得します(ロンボクは整頓できます)。

1

「3以上」について尋ねましたが、CompletableFuturesとしてリストにある場合(他の回答を参照)、この便利な方法を使用できます。

private static <T> CompletableFuture<List<T>> join(List<CompletableFuture<T>> executionPromises) {
    CompletableFuture<Void> joinedPromise = CompletableFuture.allOf(executionPromises.toArray(CompletableFuture[]::new));
    return joinedPromise.thenApply(voit -> executionPromises.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}

それはあなたの「先物のリスト」を「将来の結果リスト」に変換します。

1
Itchy

私は同様の問題を抱えていましたが、3つ以上の完全な未来があるので Holger's の答えに基づいて構築しました小さな汎用ユーティリティを作成しました。

public static <T, R> CompletableFuture<R> allOf(List<CompletableFuture<T>> args, Function<List<T>, R> combiner) {
    final Queue<CompletableFuture<T>> queue = new LinkedList<>();
    for (CompletableFuture<T> arg : args) {
        queue.add(arg);
    }
    return aggregator(queue, new ArrayList<>(), combiner);
}

private static <T, R> CompletableFuture<R> aggregator(Queue<CompletableFuture<T>> queue, List<T> arg,
        Function<List<T>, R> combiner) {
    if (queue.size() == 2)
        return queue.poll().thenCombine(queue.poll(), (c, d) -> {
            arg.add(c);
            arg.add(d);
            return combiner.apply(arg);
        });
    return queue.poll().thenCompose(data -> {
        arg.add(data);
        return aggregator(queue, arg, combiner);
    });
}
0
Krish

任意の数のCompletableFutureを組み合わせることができます(削減)

CompletionStage<A> futA = getA();
CompletionStage<B> futB = getB(); 
CompletionStage<C> futC = getC(); 

Stream.of(futA, futB, futC) 
    .reduce((f1, f2) -> f1.thenCombine(f2, (d1, d2) -> combine(d1, d2));

combineメソッドの実装は、データ値(A、B、およびC)をマージする責任があります。これは、A、B、およびCが異なる場合は注意が必要です。

0
TriCore