web-dev-qa-db-ja.com

async / awaitは、IOとCPUバインドの両方の方法に適していますか?

MSDNのドキュメントには、asyncおよびawaitがIOにバインドされたタスクに適しているのに対し、Task.Runは、CPUバウンドタスクに使用する必要があります。

私はHTTPリクエストを実行してHTMLドキュメントを取得し、それを解析するアプリケーションに取り組んでいます。私はこのようなメソッドを持っています:

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync())
        return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound
}

これはasyncawaitの適切な使用ですか、それとも使いすぎですか?

41
Sam

すでに2つの良い答えがありますが、私の0.02を追加するには...

消費非同期操作について話している場合、async/awaitはI/Oバウンドの両方でうまく機能しますとCPUバウンド。

MSDNドキュメントには、producing非同期操作に少し傾いていると思います。この場合、TaskCompletionSource(または同様の)I/Oバウンドの場合、およびCPUバウンドの場合はTask.Run(または類似)。最初のTaskラッパーを作成したら、asyncawaitによるconsumedが最適です。

あなたの特定の例では、それは本当にLoadHtmlDocumentにかかる時間にかかっています。 Task.Runを削除すると、LoadPageを呼び出すのと同じコンテキスト内で(おそらくUIスレッド上で)実行されます。 Windows 8のガイドラインでは、50ミリ秒以上かかる操作はすべてasync...にする必要があると規定しています...開発者のマシンでの50ミリ秒は、クライアントのマシンでは長くなる可能性があることに注意してください...

したがって、LoadHtmlDocumentの実行時間が50ミリ秒未満であることを保証できる場合は、直接実行できます。

public async Task<HtmlDocument> LoadPage(Uri address)
{
  using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
  using (var responseContent = httpResponse.Content)
  using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound
    return LoadHtmlDocument(contentStream); //CPU-bound
}

ただし、@ svickが言及したように、私はConfigureAwaitをお勧めします。

public async Task<HtmlDocument> LoadPage(Uri address)
{
  using (var httpResponse = await new HttpClient().GetAsync(address)
      .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
  using (var responseContent = httpResponse.Content)
  using (var contentStream = await responseContent.ReadAsStreamAsync()
      .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
    return LoadHtmlDocument(contentStream); //CPU-bound
}

ConfigureAwaitを使用すると、HTTP要求がすぐに(同期的に)完了しない場合、これにより(この場合)LoadHtmlDocumentTask.Runへの明示的な呼び出しなしにスレッドプールスレッドで実行されます。 。

このレベルでasyncのパフォーマンスに興味がある場合は、この件に関するStephen Toubの video および MSDNの記事 を確認してください。彼はたくさんの有用な情報を持っています。

41
Stephen Cleary

非同期の操作(つまり、awaitで表される)をTaskするのが適切です。

重要な点は、IO操作の場合、可能な場合は常に、Task.Runブロッキング同期メソッド。 IOの実行中にスレッド(スレッドプールスレッドを含む)をブロックしている場合、awaitモデルの真の力を活用していません。

操作を表すTaskを作成すると、それがCPUであるか、IOバインドされているかは関係ありません。呼び出し元にとっては、await- ed。

20
Servy

考慮すべき点がいくつかあります。

  • GUIアプリケーションでは、UIスレッドで実行するコードをできるだけ少なくします。その場合は、Task.Run()を使用してCPUにバインドされた操作を別のスレッドにオフロードすることをお勧めします。コードのユーザーは、必要に応じて自分でそれを行うことができます。
  • ASP.NETアプリケーションのようなものでは、UIスレッドはなく、気になるのはパフォーマンスだけです。その場合、コードを直接実行する代わりにTask.Run()を使用すると多少のオーバーヘッドが発生しますが、操作に実際に時間がかかる場合は重要ではありません。 (また、同期コンテキストに戻る際にオーバーヘッドが発生します。これが、ライブラリコードでほとんどのawaitsにConfigureAwait(false)を使用する必要があるもう1つの理由です。)
  • メソッドが非同期の場合(BTWは戻り値の型だけでなく、メソッドの名前にも反映される必要があります)、CPUにバインドされた作業であっても、同期コンテキストスレッドがブロックされないことが期待されます。

これに重みを付けるには、await Task.Run()を使用するのが正しい選択だと思います。オーバーヘッドはありますが、重要な利点もいくつかあります。

18
svick