web-dev-qa-db-ja.com

複数の非同期サービスを並行して呼び出す

非同期REST互いに依存しないサービスはほとんどありません。つまり、Service1からの応答を「待機」している間、Service2、Service3などを呼び出すことができます。

たとえば、以下のコードを参照してください。

var service1Response = await HttpService1Async();
var service2Response = await HttpService2Async();

// Use service1Response and service2Response

現在、service2Responseservice1Responseに依存しておらず、個別にフェッチできます。したがって、2番目のサービスを呼び出すために最初のサービスの応答を待つ必要はありません。

ここではParallel.ForEachを使用できないと思います。CPUにバインドされた操作ではないためです。

これら2つの操作を並行して呼び出すために、use Task.WhenAllを呼び出すことはできますか? Task.WhenAllを使用して私が目にする1つの問題は、結果が返されないことです。結果をフェッチするには、task.Resultを呼び出した後にTask.WhenAllを呼び出すことができます。これは、すべてのタスクがすでに完了しており、すべての応答をフェッチする必要があるためです。

サンプルコード:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

await Task.WhenAll(task1, task2)

var result1 = task1.Result;
var result2 = task2.Result;

// Use result1 and result2

このコードは、パフォーマンスの点で最初のコードよりも優れていますか?私が使用できる他のアプローチはありますか?

18
Ankit Vijay

Task.WhenAllを使用して見られる1つの問題は、結果が返されないことです

しかしdoes結果を返します。それらはすべて共通のタイプの配列に含まれるため、結果を使用するのが常に便利であるとは限りません。Taskに対応する配列内の項目を見つける必要があります。結果が必要であり、実際のタイプにキャストする可能性があるため、このコンテキストでは最も簡単で読みやすい方法ではないかもしれませんが、すべてのタスクからのすべての結果と共通のタイプが必要な場合あなたがそれらを扱いたいタイプであるなら、それは素晴らしいことです。

結果をフェッチするには、Task.WhenAllを呼び出した後にtask.Resultを呼び出すことができます。すべてのタスクがすでに完了しており、すべての応答をフェッチする必要があるためですか?

はい、できます。それらをawaitすることもできます(awaitは障害が発生したタスクで例外をラップ解除しますが、Resultは集計例外をスローしますが、それ以外は同じです)。

このコードは、パフォーマンスの点で最初のコードよりも優れていますか?

1つずつではなく2つの操作を同時に実行します。それが良いか悪いかは、それらの基本的な操作が何であるかに依存します。基礎となる操作が「ディスクからファイルを読み取る」場合、ディスクヘッドは1つしかなく、一度に1つの場所にしか置くことができないため、それらを並行して実行すると遅くなる可能性があります。 2つのファイル間をジャンプするのは、あるファイルを別のファイルを読み取るよりも遅くなります。一方、操作が「ネットワークリクエストを実行する」場合(ここでのケースのように)、応答を待つことができるため、操作が高速になる可能性があります(少なくとも特定の数の同時リクエストまで)。進行中の他の保留中のネットワーク要求もあるときと同じ速さで、他のネットワークコンピュータから。あなたの状況でそれがより速いかどうか知りたいなら、それをテストしてください。

私が使用できる他のアプローチはありますか?

あなたが知っていることが重要ではない場合all最初の操作だけでなく、並行して実行しているすべての操作の中でスローされた例外の場合、タスクを単純にawaitWhenAllなし。 WhenAllが与える唯一のことは、最初のフォルトタスクにヒットしたときにスローするのではなく、すべてのフォルトタスクからのすべての例外をAggregateExceptionにすることです。それは次のように簡単です:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

var result1 = await task1;
var result2 = await task2;
17
Servy

SemaphoreSlimを使用して最大の並列度を設定できる拡張メソッドを次に示します

    /// <summary>
    /// Concurrently Executes async actions for each item of <see cref="IEnumerable<typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type of IEnumerable</typeparam>
    /// <param name="enumerable">instance of <see cref="IEnumerable<typeparamref name="T"/>"/></param>
    /// <param name="action">an async <see cref="Action" /> to execute</param>
    /// <param name="maxDegreeOfParallelism">Optional, An integer that represents the maximum degree of parallelism,
    /// Must be grater than 0</param>
    /// <returns>A Task representing an async operation</returns>
    /// <exception cref="ArgumentOutOfRangeException">If the maxActionsToRunInParallel is less than 1</exception>
    public static async Task ForEachAsyncConcurrent<T>(
        this IEnumerable<T> enumerable,
        Func<T, Task> action,
        int? maxDegreeOfParallelism = null)
    {
        if (maxDegreeOfParallelism.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxDegreeOfParallelism.Value, maxDegreeOfParallelism.Value))
            {
                var tasksWithThrottler = new List<Task>();

                foreach (var item in enumerable)
                {
                    // Increment the number of currently running tasks and wait if they are more than limit.
                    await semaphoreSlim.WaitAsync();

                    tasksWithThrottler.Add(Task.Run(async () =>
                    {
                        await action(item).ContinueWith(res =>
                        {
                            // action is completed, so decrement the number of currently running tasks
                            semaphoreSlim.Release();
                        });
                    }));
                }

                // Wait for all tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

使用例:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);
0
Jay Shah