web-dev-qa-db-ja.com

ConfigureAwait(false)とTask.Runの使用の違いは何ですか?

ライブラリコード内のawaitsにConfigureAwait(false)を使用して、後続のコードが呼び出し側の実行コンテキスト(UIスレッドなど)で実行されないようにすることをお勧めします。同じ理由で、await Task.Run(CpuBoundWork)の代わりにCpuBoundWork()を使用する必要があることも理解しています。

ConfigureAwaitを使用した例

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

Task.Runを使用した例

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

これら2つのアプローチの違いは何ですか?

55
Sam

_Task.Run_と言うときは、CPUの作業が必要であり、時間がかかる可能性があるため、常にスレッドプールスレッドで実行する必要があると言っています。

ConfigureAwait(false)と言うとき、そのasyncメソッドの残りは元のコンテキストを必要としないと言っています。 ConfigureAwaitは、より最適化のヒントです。 alwaysではなく、継続がスレッドプールスレッドで実行されることを意味しません。

68
Stephen Cleary

この場合、最初のawait呼び出し(await client.GetAsync(address))は、Task.Run呼び出しの結果と同様に、呼び出し側コンテキストにまだマーシャリングされるため、Task.Runバージョンには少しオーバーヘッドがあります。

一方、最初の例では、最初のAsync()メソッドは呼び出しコンテキストへのマーシャリングを必要としないように構成されているため、継続をバックグラウンドスレッドで実行できます。そのため、呼び出し元のコンテキストへのマーシャリングは行われません。

16
Reed Copsey

@Stephenの回答に同意し、まだ混乱している場合は、下のスクリーンショットを参照してください1#ConfigureAwait(false)なし
下の画像をご覧ください。メインスレッドがラベルを更新しようとしています enter image description here

2#ConfigureAwait(false)を使用
下記のラベルを更新しようとしている作業スレッドを参照してください enter image description here

3
Pankaj Rawat

補足説明として、どちらの場合もLoadPage()はUIスレッドをstill blockできます。これは、await client.GetAsync(address)ConfigureAwait(false)に渡すタスクを作成する時間が必要だからです。また、タスクが返される前に、時間のかかる操作が既に開始されている場合があります。

可能な解決策の1つは、 hereSynchronizationContextRemoverを使用することです。

public async Task<HtmlDocument> LoadPage(Uri address)
{
    await new SynchronizationContextRemover();

    using (var client = new HttpClient())
    using (var httpResponse = await client.GetAsync(address))
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync())
        return LoadHtmlDocument(contentStream); //CPU-bound
}
1
Roman Gudkov