web-dev-qa-db-ja.com

すべてのサーバーサイドコードに対してConfigureAwaitを呼び出すためのベストプラクティス

あなたがサーバサイドコード(つまりいくつかのApiController)を持っていて、そしてあなたの関数が非同期である - それでそれらがTask<SomeObject>を返す - それはあなたがいつあなたがConfigureAwait(false)と呼ぶ関数を待つことがベストプラクティスと考えられますか?

スレッドコンテキストを元のスレッドコンテキストに戻す必要がないため、パフォーマンスが向上していることを私は読みました。ただし、ASP.NET Web Apiでは、リクエストが1つのスレッドで受信され、ApiController関数の最終結果が返されるときに、何らかの関数を待ってConfigureAwait(false)を呼び出すと、別のスレッドに移動する可能性があります。

私が話している内容の例を以下に入力しました。

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
468
Arash Emami

更新: ASP.NET CoreにはSynchronizationContext がありません。ASP.NETCoreを使用している場合は、ConfigureAwait(false)を使用するかどうかは関係ありません。

ASP.NETの「フル」または「クラシック」などには、この回答の残りの部分が適用されます。

元の投稿(コア以外のASP.NET用):

ASP.NETチームによるこのビデオでは、ASP.NETでasyncを使用するための最良の情報を得ています。

スレッドコンテキストを元のスレッドコンテキストに戻す必要がないため、パフォーマンスが向上していることを私は読みました。

これは、「同期」する必要があるUIスレッドが1つしかないUIアプリケーションにも当てはまります。

ASP.NETでは、状況はもう少し複雑です。 asyncメソッドが実行を再開すると、ASP.NETスレッドプールからスレッドを取得します。 ConfigureAwait(false)を使用してコンテキストキャプチャを無効にした場合、スレッドはそのままメソッドの実行を続けます。コンテキストキャプチャを無効にしないと、スレッドはリクエストコンテキストを再入力してからメソッドの実行を続けます。

したがって、ConfigureAwait(false)はASP.NETでのスレッドジャンプを節約しません。リクエストコンテキストを再入力する手間が省けますが、通常これは非常に高速です。 ConfigureAwait(false)couldは、リクエストを少量の並列処理で処理しようとしている場合に役立ちますが、実際にはTPLがこれらのシナリオのほとんどに適しています。

ただし、ASP.NET Web Apiでは、リクエストが1つのスレッドで受信され、何らかの関数を待ってConfigureAwait(false)を呼び出すと、ApiController関数の最終結果が返されるときに別のスレッドに移動する可能性があります。 。

実際には、awaitを実行するだけでそれを実行できます。 asyncメソッドがawaitにヒットすると、methodはブロックされますが、threadはスレッドプールに戻ります。メソッドを続行する準備が整うと、スレッドプールからスレッドが捕捉され、メソッドの再開に使用されます。

ASP.NETでConfigureAwaitがもたらす唯一の違いは、そのスレッドがメソッドの再開時に要求コンテキストに入るかどうかです。

私は私の MSDNのSynchronizationContext に関する記事と私の asyncのイントロブログ記事 )にもっと詳しい情報があります。

516
Stephen Cleary

あなたの質問に対する簡単な答え:いいえ。そのようなアプリケーションレベルでConfigureAwait(false)を呼び出すべきではありません。

TL、DR版の長い答え:あなたがあなたの消費者を知らず、同期コンテキストを必要としないライブラリを書いているなら(これは私が信じているライブラリではいけない)、あなたは常にConfigureAwait(false)を使うべきです。さもなければ、あなたのライブラリの消費者はあなたの非同期メソッドをブロックする方法で消費することによってデッドロックに直面するかもしれません。これは状況によって異なります。

これがConfigureAwaitメソッドの重要性についてのもう少し詳細な説明です(私のブログ投稿からの引用):

あなたがawaitキーワードでメソッドを待っているとき、コンパイラはあなたの代わりにたくさんのコードを生成します。このアクションの目的の1つは、UI(またはメイン)スレッドとの同期を処理することです。この機能の重要な要素は、現在のスレッドの同期コンテキストを取得するSynchronizationContext.Currentです。 SynchronizationContext.Currentは現在の環境に応じて設定されます。TaskのGetAwaiterメソッドはSynchronizationContext.Currentを検索します。現在の同期コンテキストがnullでない場合、その待機者に渡される継続はその同期コンテキストにポストバックされます。

新しい非同期言語機能を使用するメソッドをブロッキング方式で使用する場合、利用可能なSynchronizationContextがあると、デッドロックが発生します。このようなメソッドをブロッキング的に使用している場合(Waitメソッドでタスクを待機するか、タスクのResultプロパティから直接結果を取得する)、同時にメインスレッドをブロックします。最終的にタスクがスレッドプール内のそのメソッド内で完了すると、SynchronizationContext.Currentが使用可能でキャプチャーされているので、メインスレッドにポストバックするために継続を呼び出します。しかし、ここには問題があります。UIスレッドがブロックされていて、デッドロックが発生しています。

また、あなたの質問にぴったりの2つの素晴らしい記事があります。

最後に、 Lucian Wischik からの非常に短いビデオがあります: 非同期ライブラリメソッドはTask.ConfigureAwait(false)の使用を考慮する必要があります

お役に立てれば。

122
tugberk

私がConfigureAwait(false)を使って見つけた最大の欠点は、スレッドカルチャがシステムのデフォルトに戻されることです。カルチャを設定した場合.

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

カルチャがen-USに設定されているサーバーでホストしている場合は、ConfigureAwait(false)がCultureInfo.CurrentCultureからen-AUが返される前、およびen-USになった後がわかります。すなわち.

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

アプリケーションがカルチャ固有のデータフォーマットを必要とするものを実行している場合は、ConfigureAwait(false)を使用するときにこのことに注意する必要があります。

15
Mick

私はTaskの実装についていくつかの一般的な考えを持っています:

  1. タスクは使い捨てですが、 想定してはいませんusingを使用します。
  2. ConfigureAwaitは4.5で導入されました。 Taskは4.0で導入されました。
  3. .NETスレッドalwaysはコンテキストをフローするために使用されていました(CLRの本を介したC#を参照)が、Task.ContinueWithのデフォルト実装ではb/cを実行しません。
  4. 問題は、ライブラリ開発者がそのクライアントがコンテキストフローを必要としているかどうかを気にするべきではないということです。
  5. [後で追加]正式な回答や適切な言及がなく、私たちがこれについて争っているという事実は、誰かが自分の仕事を正しく行っていないことを意味します。

私はこの件に関して 投稿 をいくつか持っていますが、私の考えでは - TugberkのNice answerに加えて - すべてのAPIを非同期にして理想的にはコンテキストを流すべきです。 非同期をしているので、待つ代わりに単に継続を使うことができます。ライブラリーでは待機が行われず、フローが維持されるのでデッドロックは発生しません(HttpContextなど)。

ライブラリが同期APIを公開しているが、別の非同期APIを使用している場合に問題があります。したがって、コードでWait()/Resultを使用する必要があります。

7
Aliostad