639

サーバー側のコード (つまり、一部ApiController) があり、関数が非同期であるため、関数が返さTask<SomeObject>れる場合、呼び出した関数を待機することは常にベスト プラクティスと見なされますConfigureAwait(false)か?

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

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

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await GetCustomerAsync(id).ConfigureAwait(false);
        
        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
4

4 に答える 4

729

更新: ASP.NET Core にはSynchronizationContext. ASP.NET Core を使用している場合は、使用するかどうかは問題ではありませ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) 要求の少量の並列処理を行おうとしている場合に役立つ可能性がありますが、実際には TPL がこれらのシナリオのほとんどに適しています。

ただし、ASP.NET Web Api では、要求が 1 つのスレッドで受信され、関数を待機して ConfigureAwait(false) を呼び出すと、ApiController 関数の最終結果を返すときに別のスレッドになる可能性があります。 .

実際には、実行するだけでそれawaitが可能です。asyncメソッドが に到達するawaitと、メソッドはブロックされますが、スレッドスレッド プールに戻ります。メソッドを続行する準備が整うと、スレッド プールから任意のスレッドが取得され、メソッドを再開するために使用されます。

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

MSDN の記事SynchronizationContextasync紹介ブログの投稿に詳しい背景情報があります。

于 2012-11-21T13:40:46.267 に答える
145

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

長い答えの TL;DR バージョン: コンシューマがわからず、同期コンテキストを必要としないライブラリを作成している場合 (これは、私が信じているライブラリではすべきではありません)、常に を使用する必要がありますConfigureAwait(false)。そうしないと、ライブラリのコンシューマーが非同期メソッドをブロック方式で使用することにより、デッドロックに直面する可能性があります。これは状況によって異なります。

メソッドの重要性についてもう少し詳しく説明しConfigureAwaitます (私のブログ投稿からの引用)。

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

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

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

最後に、このトピックに関するLucian Wischikの素晴らしい短いビデオがあります: Async ライブラリ メソッドは、 Task.ConfigureAwait(false) の使用を検討する必要があります

お役に立てれば。

于 2012-11-21T09:01:21.073 に答える
13

の実装について、いくつかの一般的な考えがありTaskます。

  1. Task は使い捨てですが、使用することは想定されていませusingん。
  2. ConfigureAwait4.5 で導入されました。Task4.0で導入されました。
  3. .NET スレッドは常にコンテキストをフローするために使用されます (CLR ブックの C# を参照) が、既定の実装でTask.ContinueWithは b/c を行わず、コンテキスト スイッチが高価であり、既定でオフになっていることがわかりました。
  4. 問題は、ライブラリ開発者は、クライアントがコンテキスト フローを必要とするかどうかを気にする必要がないため、コンテキストをフローするかどうかを決定する必要がないことです。
  5. [後で追加] 正式な回答と適切な参照がなく、これについて私たちが戦い続けているという事実は、誰かが自分の仕事を正しく行っていないことを意味します.

私はこの件についていくつかの投稿をしていますが、Tugberk の素晴らしい回答に加えて、すべての API を非同期にし、理想的にはコンテキストをフローする必要があるというのが私の見解です。非同期を行っているため、待機の代わりに継続を使用するだけで、ライブラリで待機が行われず、フローを維持してコンテキスト (HttpContext など) が保持されるため、デッドロックは発生しません。

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

于 2012-11-21T13:23:29.190 に答える