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


561

サーバー側コード(つまり一部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 SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

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

回答:


628

更新: 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つのスレッドで受信され、ApiController関数の最終結果を返すときに、いくつかの関数を待ってConfigureAwait(false)を呼び出すと、別のスレッドに入る可能性があります。 。

実際には、それを行うawaitことができます。あなたいったんasyncメソッドがヒットしawaitこの方法は、ブロックされたが、されたスレッドは、スレッドプールに戻ります。メソッドを続行する準備ができると、すべてのスレッドがスレッドプールから奪取され、メソッドの再開に使用されます。

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

MSDNの記事SynchronizationContextasync紹介ブログの投稿に、より多くの背景情報があります。


23
スレッドローカルストレージは、どのコンテキストからフローされませんHttpContext.CurrentASP.NETによってフローされます。ASP.NETはSynchronizationContext、デフォルトでフローされますがawait、フローされませんContinueWith。大藤、(セキュリティ上の制限を含む)実行コンテキストは、C#でCLRに言及したコンテキストがあり、それはされて両方で流れたContinueWithawait(あなたが使用している場合でもConfigureAwait(false))。
Stephen Cleary

65
C#がConfigureAwait(false)のネイティブ言語をサポートしているとしたら、すばらしいと思いませんか?「awaitnc」(コンテキストを待たない)のようなもの。どこにでも個別のメソッド呼び出しを入力するのはかなり面倒です。:)
NathanAldenSr 2014年

19
@NathanAldenSr:かなり議論されました。新しいキーワードの問題は、ConfigureAwait実際にはタスクをawait待機しているときにのみ意味があるのに対して、「待機可能」には機能することです。考慮された他のオプションは次のとおりです。ライブラリ内の場合、デフォルトの動作はコンテキストを破棄する必要がありますか?または、デフォルトのコンテキスト動作のコンパイラ設定がありますか?これらのコードはどちらも拒否されました。コードを読んで何をするのかを伝えるのが難しいからです。
スティーブンクリアリー2014年

10
@AnshulNigam:これが、コントローラーアクションにコンテキストが必要な理由です。しかし、アクションが呼び出すほとんどのメソッドはそうではありません。
スティーブンクリアリー2014年

14
@JonathanRoeder:一般に、ASP.NET では最初に/ を使用してはならないConfigureAwait(false)ため、Result/ Waitベースのデッドロックを回避する必要はありません。ResultWait
スティーブンクリアリー2014

131

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

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

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

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

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

また、あなたの質問にぴったりの記事が2つあります。

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

お役に立てれば。


2
「タスクのGetAwaiterメソッドはSynchronizationContext.Currentを検索します。現在の同期コンテキストがnullでない場合、その待機者に渡される継続はその同期コンテキストにポストバックされます。」-私はあなたがTaskスタックを歩いてを取得するように言っているという印象を受けていますがSynchronizationContext、これは間違っています。SynchronizationContext呼び出しの前にグラブさTaskに継続され、その後、残りのコードSynchronizationContext場合SynchronizationContext.Currentはnullではありません。
casperOne 2012年

1
@casperOne私は同じことを言うつもりでした。
tugberk

8
それSynchronizationContext.Currentが明確であることを確認すること、またはクラスライブラリ全体Task.Run()に書き込む必要がなく、ライブラリが内で呼び出されることは、呼び出し元の責任ではない.ConfigureAwait(false)でしょうか?
binki 2014

1
@binki-一方、(1)おそらくライブラリは多くのアプリケーションで使用されているため、ライブラリで1回限りの労力を費やしてアプリケーションを簡単にすることはコスト効率が高くなります。(2)おそらくライブラリの作者は、元のコンテキストを継続する必要がある理由のないコードを書いたことを知ってい.ConfigureAwait(false)ます。おそらく、それがデフォルトの動作であれば、ライブラリの作成者にとっては簡単でしょう。しかし、ライブラリを正しく書くことを少し難しくする方が、アプリを正しく書くことを少し難しくするよりも良いと思います。
ToolmakerSteve 2015

4
図書館の著者が消費者を甘やかす必要があるのはなぜですか?消費者が行き詰まりを起こしたい場合、なぜ私はそれらを防ぐ必要があるのですか?
クォークリー2018

25

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)を使用するときにこれに注意する必要があります。


27
.NETの最新バージョン(4.6以降だと思いますか?)をConfigureAwait(false)使用しても、スレッド間でカルチャを伝播します。
スティーブンクリアリー2017年

1
情報をありがとう。実際に.net 4.5.2を使用しています
Mick

11

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

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

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

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


6
1)Task.Dispose必要に応じて電話をかけることができます。ほとんどの時間は必要ありません。2)TaskTPLの一部として.NET 4.0で導入されましたConfigureAwait。ときをasync添加して、彼らは既存の再利用Task代わりに新しいを発明の種類をFuture
Stephen Cleary

6
3)2つの異なるタイプの「コンテキスト」を混同している。CLRを介してC#で言及されている「コンテキスト」は、Tasks でも常にフローされます。によって制御される「コンテキスト」ContinueWithは、SynchronizationContextまたはTaskSchedulerです。これらのさまざまなコンテキストは、Stephen Toubのブログで詳しく説明されています。
Stephen Cleary

21
4)各非同期メソッドは個別に再開するため、ライブラリの作成者は、呼び出し元がコンテキストフローを必要とするかどうかを気にする必要はありません。したがって、呼び出し元がコンテキストフローを必要とする場合は、ライブラリの作成者がフローをフローしたかどうかに関係なく、フローをフローできます。
Stephen Cleary

1
最初は、質問に答えるのではなく不平を言っているようです。そして、あなたは「コンテキスト」について話しているのですが、.NETにはいくつかの種類のコンテキストがあり、どれが話しているのかがはっきりしていません。そして、あなた自身が混乱していないとしても(私はそうだと思いますが、以前はThreadsで流れていたが、もはやで流れていなかったコンテキストはないと私は信じていますContinueWith())、これはあなたの答えを読むのを混乱させます。
スビック

1
@StephenClearyはい、lib devは知る必要はありません、それはクライアントに任されています。はっきりさせたと思いましたが、言い回しがはっきりしていませんでした。
Aliostad
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.