現在、ASP .NET MVC 5、Angular.JS 1.4、Web API 2、およびEntity Framework 6に依存するWebアプリケーションを開発しています。スケーラビリティの理由から、Webアプリケーションの負荷はasync/awaitパターン:このドメインでは、CPUを集中的に使用する計算が必要であり、数秒(10秒未満)かかります。過去には、計算を高速化するためにTask.Runを使用していたチームメンバーがいました。 ASP .NET MVCまたはWeb APIコントローラーは悪いプラクティスと見なされます(スレッドはIISによって認識されないため、AppDomainリサイクルでは考慮されません=>参照 Stephen Clearyのブログ投稿 =)、彼らはConfigureAwait(false)を使用しました。
public async Task CalculateAsync(double param1, double param2)
{
// CalculateSync is synchronous and cpu-intensive (<10s)
await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);
}
CPUバインド操作のために非同期Web APIコントローラーでTask.Runを使用することでパフォーマンス上の利点はありますか?
ゼロ。なし。実際、新しいスレッドを生成することでパフォーマンスを妨げています。 Webアプリケーションのコンテキスト内では、スレッドの生成は「バックグラウンド」での実行とは異なります。これは、Webリクエストの性質によるものです。着信要求があると、要求を処理するためにスレッドがプールから取得されます。非同期を使用すると、リクエストの終了前にスレッドを返すことができますifスレッドが待機状態(アイドル)の場合のみ。作業を行うためにスレッドを生成すると、プライマリスレッドが効果的にアイドル状態になり、プールに返されるようになりますが、アクティブなスレッドがまだあります。元のスレッドをプールに返すことは、その時点では何もしません。次に、新しいスレッドが作業を終了したら、プールからメインスレッドを要求し、最後に応答を返さなければなりません。 all作業が完了するまで応答を返すことができないため、1スレッドまたは100スレッド、非同期または同期のいずれを使用しても、すべてが完了するまで応答を返すことはできません。したがって、追加のスレッドを使用しても、オーバーヘッドが追加されるだけです。
ConfigureAwait(false)は、余分なスレッドの作成を実際に回避しますか?
いいえ、またはより適切に、それはそれについてではありません。 ConfigureAwait
は最適化のヒントにすぎず、元のコンテキストがスレッドのジャンプ間で維持されるかどうかのみを決定します。長いことも短いこともありますが、スレッドの作成とは関係がなく、少なくともASP.NETアプリケーションのコンテキストでは、どちらの方法でもパフォーマンスへの影響は無視できます。
CPUバウンド操作のために非同期Web APIコントローラーで
Task.Run
を使用することでパフォーマンス上の利点はありますか?
いいえ。CPUにバインドされているかどうかは関係ありません。
Task.Run
は、ThreadPool
スレッドへの作業をオフロードします。 Web APIリクエストはすでにThreadPool
スレッドを使用しているため、理由なく別のスレッドにオフロードすることでスケーラビリティを制限しているだけです。
これは、UIスレッドが特別な単一スレッドであるUIアプリで役立ちます。
ConfigureAwait(false)は、余分なスレッドの作成を本当に回避しますか?
何らかの方法で作成するスレッドには影響しません。キャプチャされたSynchronizationContext
で再開するかどうかを設定するだけです。
CPUにバインドされた操作に非同期Web APIコントローラーでTask.Runを使用することでパフォーマンス上の利点はありますか?
本当に何が起こるか考えてください-Task.Run()
はスレッドプールにタスクを作成し、await
演算子はスレッドを解放します(スタックのすべてのメソッドも待機していると仮定しています)。これでスレッドがプールに戻り、同じタスクを取得する可能性があります!このシナリオでは、明らかにパフォーマンスの向上はありません。実際、パフォーマンスが低下しています。ただし、スレッドが別のタスクを選択した場合(おそらくこれが発生します)、別のスレッドはCalculateSync()
タスクを選択し、前者が停止した場所から続行する必要があります。そもそも元のスレッドにCalculateSync()
を実行させ、タスクを関与させず、他のスレッドに他のキューに入れられたタスクを持たせる方が理にかなっているでしょう。
ConfigureAwait(false)は、余分なスレッドの作成を本当に回避しますか?
どういたしまして。それは単に、継続が呼び出し元のコンテキストで実行されるべきではないと指摘するだけです。
考慮すべき点がもう1つあります。 APIがCPU集中型タスクを実行していると言った場合、async/await helpを使用して別のスレッドでプロセスを実行し、別の要求のためにアプリプールを解放します。 async/awaitを正しく使用すると、Web APIは1秒あたりのリクエスト数をより多く処理できることを意味します。
あなたの場合、this.CalculateSync(param1, param2)
は非同期メソッドのように見えるので、これを非同期で呼び出すにはTask.Runを使用する必要があります。
実際にパフォーマンスが低下するため、.ConfigureAwait(false)
を削除することをお勧めします。