web-dev-qa-db-ja.com

task.Resultと同じ完了したタスクを待ちますか?

現在、Stephen Clearyによる「C#Cookbookの同時実行」を読んでいますが、次の手法に気付きました。

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  

downloadTaskhttpclient.GetStringAsyncへの呼び出しであり、timeoutTaskTask.Delayを実行しています。

タイムアウトしなかった場合、downloadTaskはすでに完了しています。タスクが既に完了している場合、なぜdownloadTask.Resultを返す代わりに2回目の待機が必要なのですか?

102
julio.g

ここにはすでにいくつかの良い回答/コメントがありますが、チャイムに...

await(またはResult)よりもWaitを好む理由は2つあります。 1つ目は、エラー処理が異なることです。 awaitは、例外をAggregateExceptionにラップしません。理想的には、非同期コードは、特にwantsしない限り、AggregateExceptionを処理する必要はありません。

2番目の理由はもう少し微妙です。私のブログ(および本)で説明しているように、 Result/Waitはデッドロックを引き起こす可能性があります 、および asyncメソッドで使用するとさらに微妙なデッドロックを引き起こす可能性があります 。したがって、コードを読んでいるときにResultまたはWaitが表示された場合、それは即時の警告フラグです。 Result/Waitは、タスクが既に完了していることを確実に確信している場合にのみ正しいです。これは(実際のコードでは)一目でわかりにくいだけでなく、コードの変更により脆弱です。

それは、Result/Waitneverを使用すべきだということではありません。私は自分のコードでこれらのガイドラインに従います:

  1. アプリケーションの非同期コードはawaitのみを使用できます。
  2. (ライブラリ内の)非同期ユーティリティコードは、コードが実際に呼び出しを行う場合、Result/Waitを使用することがあります。このような使用法にはおそらくコメントが必要です。
  3. ParallelタスクコードはResultおよびWaitを使用できます。

(1)は一般的なケースであるため、awaitをすべての場所で使用し、他のケースを一般規則の例外として扱う傾向があることに注意してください。

142
Stephen Cleary

timeoutTaskTask.Delayの製品である場合、これは理にかなっています。

Task.WhenAnyTask<Task>を返します。ここで、内部タスクは引数として渡したタスクの1つです。次のように書き換えることができます。

Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)  
  return null;  
return downloadTask.Result; 

いずれの場合も、downloadTaskはすでに完了しているため、return await downloadTaskreturn downloadTask.Resultにはわずかな違いがあります。 @KirillShlenskiyがコメントで指摘したように、後者は元の例外をラップするAggregateExceptionをスローするという点にあります。前者は元の例外を再スローするだけです。

いずれの場合でも、例外を処理する場合は常に、AggregateExceptionとその内部例外をチェックして、エラーの原因を特定する必要があります。

11
noseratio