この質問の後、asyncを使うとき、それは私を快適にします ASP.NET MVCでの操作だから、私はそれについて2つのブログ記事を書いた:
私は、ASP.NET MVCでの非同期操作について誤解しすぎています。
私はいつもこの文章を聞いています:操作が非同期的に実行される場合、アプリケーションはより優れた拡張性
そして私はこの種の文章もたくさん聞いた。大量のトラフィックがある場合は、クエリを非同期に実行しない方がよい場合があります。1つの要求を処理するために2つの追加スレッドを消費すると、他の着信要求からリソースが奪われます。
この二つの文は矛盾していると思います。
私はスレッドプールがASP.NETでどのように機能するかについて多くの情報を持っていませんが、私はスレッドプールがスレッドのための限られたサイズを持っていることを知っています。したがって、2番目の文はこの問題に関連している必要があります。
そして、私はASP.NET MVCの非同期操作が.NET 4のThreadPoolからのスレッドを使用するかどうか知りたいですか?
たとえば、AsyncControllerを実装すると、アプリはどのように構成されますか。大量のトラフィックがある場合は、AsyncControllerを実装することをお勧めしますか。
私の目の前でこの黒いカーテンを取り除き、ASP.NET MVC 3(NET 4)での非同期についての話を私に説明できる人は他にいませんか?
編集する
私はこの文書を何百回も読んだことがありますが、主な取引は理解していますが、それでも矛盾するコメントが多すぎるため混乱しています。
編集する
以下のようなコントローラアクションがあるとしましょう(の実装ではありません)。AsyncController
しかし):
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
あなたがここで見るように、私は操作を発射し、それを忘れます。その後、私はそれが完了するのを待たずにすぐに戻ります。
この場合、これはthreadpoolからのスレッドを使用する必要がありますか?もしそうなら、それが完了した後、そのスレッドはどうなりますか?するGC
それが完了した直後に入ってきて掃除をする?
編集する
@ Darinの答えとして、データベースと対話する非同期コードの例を次に示します。
public class FooController : AsyncController {
//EF 4.2 DbContext instance
MyContext _context = new MyContext();
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(3);
Task<IEnumerable<Foo>>.Factory.StartNew(() => {
return
_context.Foos;
}).ContinueWith(t => {
AsyncManager.Parameters["foos"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<Bars>>.Factory.StartNew(() => {
return
_context.Bars;
}).ContinueWith(t => {
AsyncManager.Parameters["bars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<FooBar>>.Factory.StartNew(() => {
return
_context.FooBars;
}).ContinueWith(t => {
AsyncManager.Parameters["foobars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ViewResult IndexCompleted(
IEnumerable<Foo> foos,
IEnumerable<Bar> bars,
IEnumerable<FooBar> foobars) {
//Do the regular stuff and return
}
}
これが優秀品ASP.NETでの非同期処理を理解するために読むことをお勧めします(これは非同期コントローラが基本的に表すものです)。
まず標準の同期アクションを考えましょう。
public ActionResult Index()
{
// some processing
return View();
}
このアクションに対する要求が行われると、スレッドプールからスレッドが引き出され、このアクションの本体がこのスレッド上で実行されます。そのため、このアクション内の処理が遅い場合は、処理全体でこのスレッドをブロックしているため、このスレッドを他の要求の処理に再利用することはできません。要求の実行が終了すると、スレッドはスレッドプールに返されます。
それでは、非同期パターンの例を見てみましょう。
public void IndexAsync()
{
// perform some processing
}
public ActionResult IndexCompleted(object result)
{
return View();
}
要求がIndexアクションに送信されると、スレッドはスレッドプールとその本体から引き出されます。IndexAsync
メソッドが実行されます。このメソッドの本体が実行を終了すると、スレッドはスレッドプールに返されます。それから、標準を使用してAsyncManager.OutstandingOperations
非同期操作の完了を知らせると、スレッドプールとその本体から別のスレッドが引き出されます。IndexCompleted
アクションが実行され、結果がクライアントに表示されます。
したがって、このパターンでわかるのは、単一のクライアントHTTP要求が2つの異なるスレッドによって実行される可能性があるということです。
今興味深い部分はの中で起こりますIndexAsync
方法。内部にブロック操作があると、ワーカースレッドをブロックしているので、非同期コントローラの目的を完全に無駄にしていることになります(このアクションの本体は、スレッドプールから引き出されたスレッドで実行されることに注意してください)。
では、非同期コントローラを実際に利用できるのはいつでしょうか。
私たちは、I / Oの集中的な操作(データベースやネットワークへのリモートサービスへの呼び出しなど)があるときに最も多く得ることができます。あなたがCPUに負荷をかける操作をしているならば、非同期アクションはあなたに大きな利益をもたらさないでしょう。
それでは、なぜI / O集約型操作から利益を得ることができるのでしょうか。使用できたから入出力完了ポート。 IOCPは、操作全体の実行中にサーバー上のスレッドやリソースを消費しないため、非常に強力です。
どうやって動くの?
リモートWebページのコンテンツをダウンロードしたいとしましょう。WebClient.DownloadStringAsync方法。このメソッドを呼び出すと、オペレーティングシステム内にIOCPが登録され、すぐに戻ります。リクエスト全体の処理中、サーバー上でスレッドは消費されません。すべてがリモートサーバーで発生します。これには長い時間がかかるかもしれませんが、ワーカースレッドを危険にさらしていないので気にする必要はありません。応答が受信されると、IOCPが通知され、スレッドプールからスレッドが引き出され、このスレッドでコールバックが実行されます。しかしあなたが見ることができるように、全プロセスの間、私達は少しの糸も独占しなかった。
FileStream.BeginRead、SqlCommand.BeginExecuteなどのメソッドでも同じことが言えます。
複数のデータベース呼び出しを並列化するのはどうでしょうか。 4つのブロッキングデータベース呼び出しを順番に実行する同期コントローラアクションがあるとします。各データベース呼び出しに200ミリ秒かかる場合、コントローラのアクションの実行には約800ミリ秒かかることを計算するのは簡単です。
これらの呼び出しを順番に実行する必要がない場合、それらを並列化するとパフォーマンスが向上しますか?
それは大きな問題です。答えるのは簡単ではありません。たぶんそうだけどたぶん違う。それは完全にあなたがそれらのデータベース呼び出しをどのように実装するかに依存するでしょう。前述のように非同期コントローラとI / O完了ポートを使用すると、ワーカースレッドを独占することはないので、このコントローラアクションやその他のアクションのパフォーマンスも向上します。
一方、(スレッドプールからのスレッドに対して実行されるブロッキングデータベース呼び出しを使用して)それらをうまく実装しない場合は、基本的にこのアクションの実行の合計時間を約200msに短縮しますが、4つのワーカースレッドを消費します。他の要求を処理するためにプール内にスレッドが欠落しているために不足している可能性がある他の要求のパフォーマンスが低下した可能性があります。
そのため、非常に困難であり、アプリケーションに対して広範囲なテストを実行する準備ができていないと感じた場合は、非同期コントローラーを実装しないでください。理由がある場合にのみ実装してください。たとえば、標準的な同期コントローラのアクションが(もちろん広範囲の負荷テストと測定を実行した後で)アプリケーションのボトルネックになっていることがわかった場合。
今あなたの例を考えてみましょう:
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Indexアクションの要求を受け取ると、スレッドプールからその本体を実行するためのスレッドが引き出されますが、その本体は次のようにして新しいタスクをスケジュールするだけです。TPL。そのため、アクションの実行は終了し、スレッドはスレッドプールに返されます。それ以外で、TPLスレッドプールからのスレッドを使用してそれらの処理を実行します。したがって、元のスレッドがスレッドプールに返された場合でも、タスクの本体を実行するためにこのプールから別のスレッドを引き出すことになります。だからあなたはあなたの貴重なプールから2スレッドを危険にさらしました。
それでは、次のことを考えましょう。
public ViewResult Index() {
new Thread(() => {
//Do an advanced looging here which takes a while
}).Start();
return View();
}
この場合、手動でスレッドを生成しています。この場合、Indexアクションの本体の実行には少し時間がかかることがあります(新しいスレッドを生成する方が既存のプールからスレッドを描画するよりも費用がかかるため)。しかし、高度なロギング操作の実行は、プールの一部ではないスレッド上で行われます。そのため、プールからのスレッドが他の要求を処理するために解放されたままになってしまう危険性はありません。
System.Threading.Task
中を走るIndexAsync
方法。これらの操作の中で、私たちはサーバーにdb呼び出しをしています。したがって、それらはすべてI / O集約型の操作です。その場合、4つの別々のスレッドを作成しますか(またはthread-poolから4つの別々のスレッドを取得しますか)。私がマルチコアマシンを持っていると仮定すると、それらは並行して走るでしょうね。 - tugberkSqlCommand.ExecuteReader
これはブロッキングコールなので、あなたはすべてを無駄にしています。この呼び出しが実行されているスレッドをブロックしているため、このスレッドがプールからのスレッドである場合、それは非常に悪い状態です。 I / O完了ポートを使用している場合にのみ利点があります。SqlCommand.BeginExecuteReader
。何をしてもIOCPを使用しない場合は、アプリケーションの全体的なパフォーマンスへの利益よりも多くの損害を与えるので、非同期コントローラを使用しないでください。 - Darin Dimitrov_context.Foo
あなたは実際には何も実行していません。あなたは式ツリーを構築しているだけです。これには細心の注意を払ってください。クエリの実行は、結果セットの列挙を開始したときにのみ延期されます。そしてこれが見解で起こるならば、これはパフォーマンスにとって破滅的であるかもしれません。 EFクエリを積極的に実行するには.ToList()
最後に。 - Darin Dimitrov
はい - すべてのスレッドはスレッドプールから来ます。あなたのMVCアプリはすでにマルチスレッド化されています。リクエストが新しいスレッドに入ったとき、プールから取得され、リクエストを処理するために使用されます。そのスレッドは、リクエストが完全に処理され完了するまで(他のリクエストから)「ロック」されます。プールに使用可能なスレッドがない場合、要求はスレッドが使用可能になるまで待機する必要があります。
非同期コントローラがある場合でも、プールからスレッドを取得しますが、リクエストを処理している間はスレッドを放棄し、何かが発生するのを待って(そしてそのスレッドを別のリクエストに渡すことができます)再度それはプールから1つを得ます。
違いは、(スレッドが何かからの応答を待っている)長時間実行されるリクエストがある場合、基本的なリクエストでさえサービスするためにプールからのスレッドを使い果たすかもしれないということです。非同期コントローラがある場合、これ以上スレッドはありませんが、待機中のスレッドはプールに返され、他の要求を処理できます。
Aほぼ実生活の例... それはバスに乗るのと同じように考えてください。乗車を待っている人が5人いて、最初に乗車して料金を支払って座る(運転手が要求に応えて)、乗車(運転手が要求に応えている)あなたのお金を見つけなさい。あなたがあなたのポケットにぶつかると、運転手はあなたをあきらめて、次の二人を乗せます(彼らの要求に応えます)、あなたがあなたのお金を見つけると、ドライバは再びあなたと話し合い始めます(あなたの要求を完了する)あなたはやりましたが、3番目と4番目の人々はあなたが奉仕を受けている途中で奉仕されました。これは、ドライバがプールからの唯一のスレッドで、乗客がリクエストであることを意味します。 2人のドライバーがいた場合どのように動作するかを書くのは複雑すぎましたが、想像できますが...
非同期コントローラがなければ、あなたの後ろの乗客はあなたがあなたのお金を探す間、長い間待たなければならないでしょう、その間バスの運転手は仕事をしていないでしょう。
つまり、多くの人が自分のお金がどこにあるのかわからない(つまり、ドライバが要求したことに応答するのに長い時間がかかる)場合、非同期コントローラが要求のスループットを向上させることができます。 aysncコントローラがなければ、誰もが目の前の人が完全に対処されるまで待ちます。ただし、MVCでは、1つのバスに多数のバスドライバがあることを忘れないでください。非同期は自動的な選択ではありません。
ここには2つの概念があります。まず第一に、ユーザーが待たされるのを避けるために、コードを並列に実行してより高速に実行するか、別のスレッドでコードをスケジュールすることができます。あなたが持っていた例
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
2番目のカテゴリに属します。ユーザーはより速い応答を得られますが、同じ作業をしなければならずスレッド化を処理しなければならないので、サーバー上の総作業負荷はより高くなります。
これの別の例は次のようになります。
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Make async web request to twitter with WebClient.DownloadString()
});
Task.Factory.StartNew(() => {
//Make async web request to facebook with WebClient.DownloadString()
});
//wait for both to be ready and merge the results
return View();
}
要求は並行して実行されるため、ユーザーはシリアルに実行された場合と同じくらい待つ必要はありません。しかし、スレッド待機中にもコードを多数のスレッドで実行するため、シリアルで実行した場合よりも、ここではより多くのリソースを使用することに気付くはずです。
これはクライアントのシナリオではまったく問題ありません。また、同期の長い実行中のコードを新しいタスクでラップする(別のスレッドで実行する)こともよくあります。また、uiの応答性を高めたり、並列化して高速化したりします。スレッドはまだずっと使用されています。高負荷のサーバーでは、実際にはより多くのリソースを使用するため、これは失敗する可能性があります。これは人々があなたについて警告したことです
MVCの非同期コントローラには別の目標があります。ここで重要なのは、何もしないでスレッドが動かないようにすることです(スケーラビリティを損なう可能性があります)。あなたが呼んでいるAPIが非同期メソッドを持っている場合、それは本当に重要です。 WebClient.DowloadStringAsync()と同じです。
重要なのは、Webリクエストが終了するまで同じリクエストまたは新しいスレッドを取得してリクエストを終了するまで、新しいリクエストを処理するためにスレッドを返させることができるということです。
非同期と並列の違いを理解してください。並列コードを、スレッドが存在する場所にあるコードと考えて、結果を待ちます。非同期コードは、コードが完了したときに通知を受けて元の作業に戻ることができるコードですが、その間、スレッドは他の作業を行うことができます。
アプリケーションできる操作が非同期的に実行される場合は拡張性が向上します追加操作にサービスを提供するために使用可能なリソースがある場合のみ。
非同期操作では、既存のアクションが進行中であるためにアクションがブロックされることはありません。 ASP.NETには、複数の要求を並行して実行できる非同期モデルがあります。要求をキューに入れてFIFOで処理することは可能ですが、何百もの要求がキューに入れられており、各要求の処理に100ミリ秒かかる場合は、これはうまくいきません。
大量のトラフィックがある場合は、よろしくクエリを非同期的に実行しない方がよいでしょう。要求を処理するための追加リソースがない可能性があるため。予備のリソースがないと、要求はキューに入れられたり、指数関数的に長くなったり、完全に失敗したりします。その場合、非同期オーバーヘッド(ミューテックスとコンテキスト切り替え操作)で何も得られません。
ASP.NETに関しては、あなたには選択の余地はありません。非同期モデルを使用しています。これは、サーバークライアントモデルにとって意味があるからです。すべてのリクエスト間で共有されるリソースを管理しようとしているのでなければ、非同期パターンを使用して独自のコードを内部的に作成して拡張性を高めようとした場合、実際には改善されません。他に何もブロックしない非同期プロセスで。
最終的には、システムにボトルネックを引き起こしている原因を実際に確認するまでは、主観的なことです。非同期パターンが役立つことが明らかな場合があります(キューに入れられたリソースのブロックを防ぐことによって)。最終的には、システムの測定と分析のみが、どこで効率が向上するかを示すことができます。
編集する
あなたの例では、Task.Factory.StartNew
callは.NETスレッドプールで操作をキューに入れます。スレッドプールスレッドの性質は、再利用することです(多数のスレッドを作成または破棄するコストを回避するため)。操作が完了すると、スレッドはプールに解放されて別の要求で再利用されます(操作でオブジェクトを作成しない限り、Garbage Collectorは実際には関与しません。その場合は通常どおりに収集されます)。スコーピング)。
ASP.NETに関する限り、ここでは特別な操作はありません。 ASP.NET要求は、非同期タスクに関係なく完了します。唯一の懸念はあなたのスレッドプールが飽和しているかどうかであるかもしれません(すなわち、現在要求を処理するために利用可能なスレッドがなく、プールの設定はそれ以上のスレッドを作成することを許可しません)。タスクの開始を待っていますプールスレッドが利用可能になるまで。
Task.Factory.StartNew
callは.NETスレッドプールで操作をキューに入れます。。この文脈では、どちらが正しいですか。1 - )新しいスレッドを作成し、それが完了すると、そのスレッドはthreadpoolに戻り、そこで再び使用されるのを待ちます。2-)スレッドプールからスレッドを取得し、そのスレッドはスレッドプールに戻り、そこで再び使用されるのを待ちます。3-)それは最も効率的なアプローチを取り、それらのどちらかをすることができます。 - tugberk
はい、それらはスレッドプールからのスレッドを使用します。実際にはMSDNからの非常に優れたガイドがあり、あなたのすべての質問などに対処します。私はそれが過去に非常に有用であるとわかりました。見てみな!
http://msdn.microsoft.com/en-us/library/ee728598.aspx
一方で、非同期コードに関してあなたが聞いているコメント+提案は、一粒の塩でなされるべきです。初心者にとっては、何かを非同期にしただけでは必ずしもスケーラビリティが向上するわけではなく、場合によってはアプリケーションのスケーラビリティが低下することもあります。あなたが "大量のトラフィック..."について投稿した他のコメントも、特定の文脈においてのみ正しいです。それは本当にあなたの操作が何をしているか、そしてそれらがシステムの他の部分とどのようにやり取りするかにかかっています。
つまり、多くの人が非同期について多くの意見を持っていますが、文脈の外では正しくないかもしれません。私はあなたの正確な問題に焦点を当てて、非同期コントローラなどが実際にどのように扱っているかを見るために基本的な性能テストをしたいと思います。きみの応用。
GC
私のアプリがメモリリークを起こさないようにするために、完了したらすぐにそれらを削除してきれいにします。その部分についての任意のアイデア。 - tugberk
まず最初はMVCではなく、スレッドプールを管理するIISです。そのため、MVCまたはASP.NETアプリケーションに送信される要求はすべて、スレッドプールで管理されているスレッドから処理されます。アプリAsynchを作成した場合にのみ、彼は別のスレッドでこのアクションを呼び出し、他の要求を処理できるようにすぐにスレッドを解放します。
私は詳細ビデオで同じことを説明しました(http://www.youtube.com/watch?v=wvg13n5V0V0/「MVC Asynchコントローラとスレッド枯渇」は、MVCでスレッド枯渇がどのように発生し、MVC Asynchコントローラを使用して最小化されるかを示します。MVCasynchとMVCの要求キューがどのように減少するかを確認できます。同期操作にとって最悪の状況です。