web-dev-qa-db-ja.com

タスクの結果を待っている間はどうなりますか?

HttpClientを使用して、.NET 4.0プロジェクトのリモートサービスにデータを送信しています。この操作のブロックは気にしないので、ContinueWithまたはasync/awaitをスキップしてResultを使用できると考えました。

デバッグ中に、リモートサーバーが応答しないという問題が発生しました。コードをステップ実行すると、コードが3行目で実行を停止したように見えました...現在のスタックポインター行が黄色で強調表示されなくなり、次の行に進みませんでした。消えたばかりです。リクエストがタイムアウトになるのを待つ必要があることに気付くまでに時間がかかりました。

var client = new HttpClient();
var task = client.PostAsync("http://someservice/", someContent);
var response = task.Result;

私の理解では、タスクでResultを呼び出すと、コードが同期的に実行され、次のように動作します(HttpClientにPostメソッドがないことを知っています)。

var client = new HttpClient();
var response = client.Post("http://someservice/", someContent);

私はこれが悪いことだとは思いませんが、ただ頭を動かそうとしています。 HttpClientが結果の代わりにタスクを直接返しているという事実により、私のアプリケーションはそれを回避していると思う場合でも非同期を自動的に利用しているというのは本当ですか?

36
scottt732

Windowsでは、すべてのI/Oは非同期です。同期APIは単なる便利な抽象化です。

したがって、_HttpWebRequest.GetResponse_を使用すると、実際に発生するのは、I/Oが(非同期で)開始され、呼び出しスレッドが(同期で)ブロックされ、完了するのを待機することです。

同様に、HttpClient.PostAsync(..).Resultを使用すると、I/Oが(非同期的に)開始され、呼び出しスレッドが(同期的に)ブロックされて、完了を待機します。

私は通常、次の理由から_Task.Result_や_Task.Wait_ではなくawaitを使用することをお勧めします。

  1. Taskメソッドの結果であるasyncでブロックすると、 簡単にデッドロック状態になる になります。
  2. _Task.Result_および_Task.Wait_は、例外をAggregateExceptionでラップします(これらのAPIはTPLからのホールドオーバーであるため)。したがって、エラー処理はより複雑です。

ただし、これらの制限を認識している場合、Taskでのブロッキングが役立つ場合があります(たとえば、コンソールアプリケーションのMain)。

43
Stephen Cleary

タスクの結果をキャプチャすると、現在のスレッドがブロックされます。この場合、メソッドの非同期バージョンを使用しても意味がありません。 Post()PostAsync().Resultは両方ともブロックします。

並行性を利用する場合は、次のように記述する必要があります。

_async Task PostContent()
{
  var client = new HttpClient();
  Task t = await client.PostAsync("http://someservice/", someContent);
  //code after this line will execute when the PostAsync completes.
  return t;
}
_

PostContent()自体がタスクを返すため、それを呼び出すメソッドも待機する必要があります。

_async void ProcessResult()
{
   var result = await PostContent(); 
   //Do work with the result when the result is ready 
}
_

たとえば、ボタンクリックハンドラーでProcessResult()を呼び出すと、UIが引き続き応答し、他のコントロールが機能することがわかります。

2
AtillaBaspinar