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


17

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

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

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

// Use service1Response and service2Response

現在、にservice2Response依存せず、service1Response独立して取得できます。したがって、最初のサービスの応答を待って2番目のサービスを呼び出す必要はありません。

Parallel.ForEachCPUバウンド操作ではないため、ここで使用できるとは思わない。

これらの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

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


I do not think I can use Parallel.ForEach here since it is not CPU bound operation-私はそこに論理を見ません。並行性は並行性です。
ロバートハーベイ

3
@RobertHarvey懸念は、このコンテキストでParallel.ForEachasync await、単一スレッドですべてを行うのに対して、新しいスレッドを生成することだと推測しています。
メタファイト

@Ankitは、コードをブロックするのに適切な場合に依存します。2番目の例は、両方の応答の準備ができるまでブロックします。最初の例は、おそらく、コードが応答(await)を使用する前に、準備ができる前に論理的にブロックするだけです。
メタファイト

両方のサービスレスポンスを消費するコードの抽象的でない例を提供した場合、より満足のいく答えを提供する方が簡単かもしれません。
メタファイト

@MetaFight 2番目の例では、.Resultが呼び出される前にすべてのタスクを完了するという考えをするWhenAll前に、私がやっていResultます。Task.Resultは呼び出し元のスレッドをブロックするため、タスクが実際に完了した後に呼び出した場合、すぐに結果が返されると想定しています。理解を検証したい。
アンキットビジェイ

回答:


17

Task.WhenAllを使用すると、結果が返されないという問題があります

ただし、結果返されます。それらはすべて共通の型の配列にあるため、結果を使用したい場合に対応する配列内のアイテムを検索し、潜在的にその結果にキャストする必要があるため、結果を使用することは必ずしも有用ではありTaskません実際のタイプなので、このコンテキストでは最も簡単で最も読みやすいアプローチではないかもしれませんが、すべてのタスクからすべての結果を取得したいだけで、共通タイプがそれらを扱いたいタイプである場合、それは素晴らしいです。

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

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

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

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

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

それはあなたが知っていることがあなたにとって重要ではない場合は、すべてあなたが並行してやっているすべての操作だけではなく、最初の1の間で発生した例外のを、あなたは、単にすることができawaitずにタスクWhenAllのすべてで。唯一のことはWhenAll、あなたが持っている与えAggregateException、すべての障害の発生したタスクから一つ一つの例外を除いて、というよりも、あなたが最初の障害の発生したタスクを打ったときに投げます。次のように簡単です:

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

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

それはタスクを並行して実行することはもちろん、並行して実行することではありません。各タスクが順番に完了するのを待っています。パフォーマンスコードを気にしないのであれば、まったく問題ありません。
リックオシェイ

3
RickO'Shea @それは始まり順次動作を制御します。最初の操作を*開始し後、2番目の操作開始します。しかし、開始非同期操作すること(そうでない場合、それは実際には非同期ではありません、それはその方法ではバグです)基本的に瞬時にする必要があります。一方を開始してからもう一方を開始した後、最初が終了してから2番目が終了するまで続行しません。1番目が完了するのを待ってから2番目を開始することはないため、それらの同時実行を停止することはありません(並列実行と同じです)。
セルビー

@Servyそれは本当だとは思いません。それぞれ約1秒かかった2つの非同期操作(両方ともhttp呼び出しを行う)にロギングを追加し、提案どおりに呼び出し、十分なtask1が開始および終了してからtask2が開始および終了したことを確認しました。
マットフレイア

@MattFrearその場合、メソッドは実際には非同期ではありませんでした。同期的でした。 定義により、非同期メソッドは、操作が実際に終了した後に戻るのではなく、すぐに戻ります。
セルビー

定義により、@ Servyは、非同期タスクが完了するまで待ってから次の行を実行することを意味します。そうじゃない?
マットフレアー

0

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);

-2

どちらかを使用できます

Parallel.Invoke(() =>
{
    HttpService1Async();
},
() =>
{   
    HttpService2Async();
});

または

Task task1 = Task.Run(() => HttpService1Async());
Task task2 = Task.Run(() => HttpService2Async());

//If you wish, you can wait for a particular task to return here like this:
task1.Wait();

なぜ投票するのですか?
user1451111
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.