タスクの実行順序を気にせず、それらすべてを実行する必要がある場合は、引き続き使用します。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();
}
}
}
はい、使いますWhenAll
すべてのエラーを一度に伝播するからです。複数の待機を使用すると、以前の待機のいずれかがスローされた場合にエラーを失うことになります。
もう1つの重要な違いは、WhenAllが待つことです。すべて完了するタスクのチェーンawait
中止する待っている最初の例外はありますが、待機していないタスクの実行は続行されます。これは予期しない同時性を引き起こします。
私はそれがまたあなたが望む意味論が直接コードで文書化されているのでコードを読むことをもっと簡単にすると思う。
await
その結果 - svick
私の理解は好む主な理由ですTask.WhenAll
複数にawait
sはパフォーマンス/タスクの「かき回し」です。DoWork1
methodは次のようになります。
対照的に、DoWork2
これを行います:
これがあなたの特定のケースに十分な大きさの取引であるかどうかは、もちろん「文脈依存」です(駄洒落を許してください)。
非同期メソッドはステートマシンとして実装されています。メソッドがステートマシンにコンパイルされないようにメソッドを書くことは可能です。これはしばしばファーストトラック非同期メソッドと呼ばれます。これらは以下のように実装できます。
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);
}
この質問に対する他の回答は、技術的な理由を示しています。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);
それはあなたの意図をより明確に伝え、コードへの善意の更新から発生する可能性がある、あるいは単にマージが誤って行われる可能性がある特有のバグに対してそれほど脆弱ではないのでより保守可能です。
(免責事項:この回答は、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)
{
// ...
}
}
await t1; await t2; ....; await tn
=>どちらの場合も、2番目の方法が常に最良の選択です。 - cuongle