84

タスクの実行順序を気にせず、それらすべてを実行する必要がある場合は、引き続き使用します。await Task.WhenAll複数ではなくawait?例えば、DoWork2好ましい方法の下にDoWork1(なぜ?):

using System;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static async Task<string> DoTaskAsync(string name, int timeout)
        {
            var start = DateTime.Now;
            Console.WriteLine("Enter {0}, {1}", name, timeout);
            await Task.Delay(timeout);
            Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
            return name;
        }

        static async Task DoWork1()
        {
            var t1 = DoTaskAsync("t1.1", 3000);
            var t2 = DoTaskAsync("t1.2", 2000);
            var t3 = DoTaskAsync("t1.3", 1000);

            await t1; await t2; await t3;

            Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }

        static async Task DoWork2()
        {
            var t1 = DoTaskAsync("t2.1", 3000);
            var t2 = DoTaskAsync("t2.2", 2000);
            var t3 = DoTaskAsync("t2.3", 1000);

            await Task.WhenAll(t1, t2, t3);

            Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }


        static void Main(string[] args)
        {
            Task.WhenAll(DoWork1(), DoWork2()).Wait();
        }
    }
}


  • 実際にいくつのタスクを並行して実行する必要があるのかわからない場合はどうなりますか。 1000個のタスクを実行する必要がある場合はどうなりますか?最初のものはあまり読めないでしょうawait t1; await t2; ....; await tn=>どちらの場合も、2番目の方法が常に最良の選択です。 - cuongle
  • あなたのコメントは理にかなっています。私は最近自分が何かを明確にしようとしていました。答えた。その場合、3つのタスクがありました。 - avo

5 답변


80

はい、使いますWhenAllすべてのエラーを一度に伝播するからです。複数の待機を使用すると、以前の待機のいずれかがスローされた場合にエラーを失うことになります。

もう1つの重要な違いは、WhenAllが待つことです。すべて完了するタスクのチェーンawait中止する待っている最初の例外はありますが、待機していないタスクの実行は続行されます。これは予期しない同時性を引き起こします。

私はそれがまたあなたが望む意味論が直接コードで文書化されているのでコードを読むことをもっと簡単にすると思う。


  • 「それはすべてのエラーを一度に伝播するからです」awaitその結果 - svick
  • Taskで例外をどのように管理するのかという問題に関しては、この記事ではその背後にある推論について簡単に説明していますが、複数の待機とは対照的にWhenAllの利点についても触れておきます。blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx - Oskar Lindberg
  • @OskarLindbergは、OPが最初のタスクを待つ前に、すべてのタスクを開始しています。だから彼らは同時に走ります。リンクをありがとう。 - usr
  • @usr WhenAllが同じSynchronizationContextを保存するような巧妙なことをしないとセマンティクス以外にその利点をさらに広げることができないかどうか私はまだ知りたかった。決定的な文書は見つかりませんでしたが、ILを見ると、明らかにIAsyncStateMachineの異なる実装が使用されています。私はそれほどILをよく読んでいませんが、最低でもWhenAllはより効率的なILコードを生成するように見えます。 (いずれにせよ、WhenAllの結果が私に関係するすべてのタスクの状態を反映しているという事実だけで、ほとんどの場合それを好むのに十分な理由です。) - Oskar Lindberg
  • 別の重要な違いは、例えばt1またはt2が例外を投げたりキャンセルされたりしても、WhenAllがすべてのタスクが完了するのを待つということである。 - Magnus

20

私の理解は好む主な理由ですTask.WhenAll複数にawaitsはパフォーマンス/タスクの「かき回し」です。DoWork1methodは次のようになります。

  • 与えられたで始めるコンテキスト
  • コンテキストを保存する
  • t1を待つ
  • 元のコンテキストを復元する
  • コンテキストを保存する
  • t2を待つ
  • 元のコンテキストを復元する
  • コンテキストを保存する
  • t3を待つ
  • 元のコンテキストを復元する

対照的に、DoWork2これを行います:

  • 与えられたコンテキストから始める
  • コンテキストを保存する
  • t1、t2、t3のすべてを待つ
  • 元のコンテキストを復元する

これがあなたの特定のケースに十分な大きさの取引であるかどうかは、もちろん「文脈依存」です(駄洒落を許してください)。


  • 同期コンテキストへのメッセージの送信はコストがかかると思われるようです。実際には違います。あなたはキューに追加されるデリゲートを持っていて、そのキューが読み込まれデリゲートが実行されます。これが追加するオーバーヘッドは正直に非常に小さいです。それは何もないわけではありませんが、それも大きくはありません。非同期操作が何であれ、その費用は、ほとんどすべての場合においてそのようなオーバーヘッドを少なくするでしょう。 - Servy
  • 同意する、それが私が1つを他よりも好むと考えることができる唯一の理由であった。それに、それに加えて、スレッド切り替えがより重要なコストであるTask.WaitAllとの類似性があります。 - Marcel Popescu
  • @ Servy As Marcelは、本当に依存していると指摘しています。たとえば、原則としてすべてのdbタスクに対してawaitを使用し、そのdbがasp.netインスタンスと同じマシン上に存在する場合は、dbヒットが発生するのを待つ必要があります。メモリのインインデックスは、その同期スイッチとスレッドプールシャッフルよりも安価です。そのようなシナリオでWhenAll()を使用すると、全体的に大きな勝利がもたらされる可能性があるので、それは実際には依存します。 - Chris Moschini
  • @ChrisMoschiniサーバーと同じマシン上にあるDBにヒットしたとしても、DBクエリがメッセージポンプに数人のデリゲートを追加するオーバーヘッドよりも速くなることはないでしょう。そのインメモリクエリはまだかなり確実に遅くなるでしょう。 - Servy
  • また、t1が遅く、t2とt3が速い場合、もう一方がすぐに戻るのを待ちます。 - David Refaeli

14

非同期メソッドはステートマシンとして実装されています。メソッドがステートマシンにコンパイルされないようにメソッドを書くことは可能です。これはしばしばファーストトラック非同期メソッドと呼ばれます。これらは以下のように実装できます。

public Task DoSomethingAsync()
{
    return DoSomethingElseAsync();
}

使用するときTask.WhenAll呼び出し側がすべてのタスクが完了するのを待つことができることを保証しながら、このファストトラックコードを維持することが可能です。

public Task DoSomethingAsync()
{
    var t1 = DoTaskAsync("t2.1", 3000);
    var t2 = DoTaskAsync("t2.2", 2000);
    var t3 = DoTaskAsync("t2.3", 1000);

    return Task.WhenAll(t1, t2, t3);
}


2

この質問に対する他の回答は、技術的な理由を示しています。await Task.WhenAll(t1, t2, t3);が好ましいです。この答えは、同じ結論に到達しながら、より柔らかい側(@usrがほのめかしている側)からそれを見ることを目的としています。

await Task.WhenAll(t1, t2, t3);それは意図を宣言し、アトミックであるため、より機能的なアプローチです。

ありawait t1; await t2; await t3;チームメイト(あるいはあなたの将来の自分でさえ)が個人間にコードを追加するのを妨げるものは何もありません。awaitステートメントもちろん、これを1行に圧縮して基本的にそれを達成しましたが、それでも問題は解決しません。また、チーム設定では、特定のコード行に複数のステートメントを含めるのは一般的に不適切な形式です。ソースファイルが人間の目でスキャンしにくくなる可能性があるためです。

簡単に言えば、await Task.WhenAll(t1, t2, t3);それはあなたの意図をより明確に伝え、コードへの善意の更新から発生する可能性がある、あるいは単にマージが誤って行われる可能性がある特有のバグに対してそれほど脆弱ではないのでより保守可能です。


1

(免責事項:この回答は、Ian Griffiths氏のTPL Asyncコースの講義/講義から得られたものです。複数サイト

WhenAllを好むもう1つの理由は、例外処理です。

DoWorkメソッドにtry-catchブロックがあり、それらが異なるDoTaskメソッドを呼び出していたとします。

static async Task DoWork1() // modified with try-catch
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        await t1; await t2; await t3;

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
    }
    catch (Exception x)
    {
        // ...
    }

}

この場合、3つのタスクすべてが例外をスローすると、最初のものだけが捕捉されます。それ以降の例外はすべて失われます。すなわちt2とt3が例外をスローした場合は、t2だけが捕捉されます。その後のタスクの例外は監視されません。

WhenAllの場合と同じように、タスクの一部またはすべてに障害が発生した場合、結果のタスクにはすべての例外が含まれます。 awaitキーワードは依然として最初の例外を常にスローします。そのため、他の例外はまだ事実上観察されていません。これを克服する1つの方法は、WhenAllタスクの後に空の継続を追加し、そこで待機を置くことです。このようにして、タスクが失敗した場合、resultプロパティは完全なAggregate Exceptionをスローします。

static async Task DoWork2() //modified to catch all exceptions
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        var t = Task.WhenAll(t1, t2, t3);
        await t.ContinueWith(x => { });

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
    }
    catch (Exception x)
    {
        // ...
    }
}

リンクされた質問


関連する質問

最近の質問