web-dev-qa-db-ja.com

トラフィックの多いシナリオでASP.NETでThreadPool.QueueUserWorkItemを使用する

私は常に、ASP.NETでも(重要ではない)短命のバックグラウンドタスクにThreadPoolを使用することがベストプラクティスと見なされているという印象を受けていましたが、その後に遭遇しました この記事 それはそうでないことを示唆しているようです-引数は、ASP.NET関連のリクエストを処理するためにThreadPoolを残すべきだということです。

それで、これまで私が小さな非同期タスクを実行してきた方法を示します。

ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))

そして、 記事 は、代わりに次のようなスレッドを明示的に作成することを提案しています。

new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()

最初の方法には管理と制限があるという利点がありますが、ASP.NETリクエストハンドラーを使用してバックグラウンドタスクがスレッドを奪い合う可能性があります(記事が正しい場合)。 2番目の方法はThreadPoolを解放しますが、無制限であるため、潜在的に多くのリソースを使い果たします。

だから私の質問は、記事のアドバイスは正しいですか?

あなたのサイトが非常に多くのトラフィックを受け取っていてThreadPoolがいっぱいになった場合、帯域外に行く方が良いでしょうか、それとも完全なThreadPoolはリソースの限界に達していることを意味しますか?独自のスレッドを開始しようとしてはいけませんか?

明確化:個別のプロセスを必要とする高価な作業項目ではなく、小さな重要ではない非同期タスク(たとえば、リモートロギング)の範囲を求めています(これらの場合、より堅牢なソリューションが必要になることに同意します)。

109
Michael Hart

ここでの他の答えは、最も重要な点を省いているようです:

低負荷サイトでより高速に処理するためにCPUを集中的に使用する操作を並列化しようとしていない限り、ワーカースレッドを使用しても意味がありません。

これは、new Thread(...)によって作成されたフリースレッドと、ThreadPoolリクエストに応答するQueueUserWorkItem内のワーカースレッドの両方に当てはまります。

はい、それは本当です。ASP.NETプロセスでThreadPoolを大量のワークアイテムをキューイングすることにより、can飢えさせることができます。 ASP.NETがそれ以上のリクエストを処理するのを防ぎます。その点で記事の情報は正確です。 QueueUserWorkItemに使用されるのと同じスレッドプールも要求の処理に使用されます。

しかし、実際にこの飢vを引き起こすのに十分な作業項目をキューに入れている場合、shouldスレッドプールを飢えさせる必要があります!文字通り何百ものCPU集中型の操作を同時に実行している場合、マシンがすでに過負荷になっているときに、ASP.NET要求を処理するために別のワーカースレッドを使用するとどうなりますか?このような状況に陥った場合、完全に再設計する必要があります!

ASP.NETでマルチスレッドコードが不適切に使用されていることを私はよく見たり聞いたりしますが、CPUを集中的に使用する作業をキューイングするためではありません。これは、I/Oにバインドされた作業をキューに入れるためのものです。 I/O作業を行う場合は、I/Oスレッド(I/O完了ポート)を使用する必要があります。

具体的には、使用しているライブラリクラスでサポートされている非同期コールバックを使用する必要があります。これらのメソッドは常に非常に明確にラベル付けされています。 BeginEndで始まります。 Stream.BeginReadSocket.BeginConnectWebRequest.BeginGetResponseなどのように。

これらのメソッドdoThreadPoolを使用しますが、IOCPを使用します。IOCPはnot ASP.NETと干渉しますリクエスト。これらは、I/Oシステムからの割り込み信号によって「起こされる」ことができる特別な種類の軽量スレッドです。また、ASP.NETアプリケーションでは、通常、ワーカースレッドごとに1つのI/Oスレッドがあるため、すべてのリクエストで1つの非同期操作をキューに入れることができます。これは、文字どおり何百もの非同期操作であり、パフォーマンスが大幅に低下することはありません(I/Oサブシステムが維持できると仮定した場合)。それはあなたが必要とする以上の方法です。

Asyncdelegatesはこの方法では動作しないことに注意してください-ThreadPool.QueueUserWorkItemのようにワーカースレッドを使用することになります。これを実行できるのは、.NET Frameworkライブラリクラスの組み込みの非同期メソッドのみです。あなたは自分でそれを行うことができますが、それは複雑で少し危険であり、おそらくこの議論の範囲を超えています。

私の意見では、この質問に対する最良の答えは、ThreadPoolまたはバックグラウンドThreadインスタンスを使用しないことです。 ASP.NET。これは、Windowsフォームアプリケーションでスレッドをスピンアップするようなものではありません。UIの応答性を維持するためにスレッドを実行し、その効率を気にしないでください。 ASP.NETでの懸念はthroughputであり、すべてのワーカースレッドでのコンテキスト切り替えはすべてkillThreadPoolを使用するかどうかにかかわらず、スループットが向上します。

ASP.NETでスレッドコードを記述している場合は、既存の非同期メソッドを使用するように書き換えられるかどうかを検討してください。できない場合は、本当にコードが本当に必要かどうかを検討してください。バックグラウンドスレッドで実行する。ほとんどの場合、おそらく純益のない複雑さを追加することになります。

103
Aaronaught

MicrosoftのASP.NETチームのThomas Marquadt氏によると、ASP.NET ThreadPool(QueueUserWorkItem)を使用しても安全です。

記事から

Q)ASP.NETアプリケーションがCLR ThreadPoolスレッドを使用している場合、CLR ThreadPoolを使用してリクエストを実行するASP.NETが不足することはありませんか? Blockquote

A)[T] o要約すると、ASP.NETのスレッドが枯渇する心配はありません。ここに問題があると思われる場合はお知らせください。対処します。

Q)独自のスレッド(新しいスレッド)を作成する必要がありますか? ASP.NETはCLR ThreadPoolを使用しているため、これは優れていません。

A)しないでください。または、別の言い方をすれば、いいえ!!!あなたが本当に賢いなら-私よりずっと賢いなら-あなたはあなた自身のスレッドを作成することができます;そうでなければ、それについても考えないでください。以下に、新しいスレッドを頻繁に作成しない理由をいくつか示します:

1)QueueUserWorkItemと比較して非常に高価です...ところで、CLRよりも優れたThreadPoolを記述できる場合は、Microsoftの仕事に応募することをお勧めします。間違いなくあなたのような人を探しています!

45
Tim P.

ASP.NETでの迅速で優先度の低い非同期作業の一般的なプラクティスは、特にリソースを制限したい高トラフィックのシナリオでは、.NETスレッドプールを使用することだと思います。

また、スレッドの実装は隠されています-独自のスレッドの生成を開始する場合は、それらも適切に管理する必要があります。できないとは言わないが、なぜその車輪を再発明するのか?

パフォーマンスが問題になり、スレッドプールが制限要因(データベース接続、送信ネットワーク接続、メモリ、ページタイムアウトなどではない)であることを確認できる場合は、スレッドプール構成を調整して、より多くのワーカースレッド、より高いキュー要求を許可しますなど.

パフォーマンスの問題がない場合、ASP.NET要求キューとの競合を減らすために新しいスレッドを生成することを選択するのは、古典的な時期尚早な最適化です。

理想的には、ロギング操作を行うために別のスレッドを使用する必要はありません-元のスレッドができるだけ早く操作を完了するようにするだけで、MSMQと別のコンシューマスレッド/プロセスが登場します。これは実装するのにより重く、より多くの作業があることに同意しますが、ここでは本当に耐久性が必要です-共有されたメモリ内キューのボラティリティはすぐに歓迎されなくなります。

4
Sam

ウェブサイトはスレッドの生成を避けてください。

通常、この機能をWindowsサービスに移動してから、それを使用して通信します(MSMQを使用して通信します)。

-編集

ここで実装を説明しました: ASP.NET MVC Webアプリケーションでのキューベースのバックグラウンド処理

-編集

これが単なるスレッドよりも優れている理由を拡大するには:

MSMQを使用すると、別のサーバーと通信できます。マシン間でキューに書き込むことができるので、何らかの理由でバックグラウンドタスクがメインサーバーのリソースを使い果たしていると判断した場合、それを非常に簡単にシフトできます。

また、実行しようとしていたタスク(メールの送信など)をバッチ処理することもできます。

4
Noon Silk

QueueUserWorkItemを使用し、ペストを回避するように新しいスレッドを作成しないでください。 ASP.NETが同じThreadPoolを使用するため、ASP.NETを飢えさせない理由を説明するビジュアルについては、6本のボーリングピン、剣、または飛行中のものを両手で保持する非常に熟練したジャグラーを想像してください。独自のスレッドの作成が悪い理由を視覚的に理解するために、高速道路への入り口ランプを頻繁に使用するラッシュアワーのシアトルで何が起こるかを想像してください。最後に、詳細な説明については、次のリンクを参照してください。

http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx

ありがとう、トーマス

2
Thomas

先週職場で同様の質問をされましたが、同じ答えをお送りします。リクエストごとにマルチスレッドWebアプリケーションを使用するのはなぜですか? Webサーバーは、多くのリクエストをタイムリーに(つまり、マルチスレッドで)提供するために大幅に最適化された素晴らしいシステムです。 Web上のほぼすべてのページをリクエストするとどうなるかを考えてください。

  1. 一部のページに対してリクエストが行われます
  2. Htmlが返される
  3. Htmlは、クライアントにさらにリクエスト(js、css、画像など)を行うように指示します。
  4. 詳細情報が提供されます

リモートロギングの例を挙げますが、それはロガーの関心事です。タイムリーにメッセージを受信するには、非同期プロセスを配置する必要があります。サムは、ロガー(log4net)がすでにこれをサポートしているはずだと指摘しています。

Samは、CLRでスレッドプールを使用してもIISのスレッドプールで問題が発生しないという点でも正しいです。ただし、ここで気にすることは、プロセスからスレッドを生成するのではなく、IIS threadpool threadsから新しいスレッドを生成することです。違いがあり、区別が重要です。

スレッドvsプロセス

スレッドとプロセスはどちらも、アプリケーションを並列化する方法です。ただし、プロセスは、独自の状態情報を含み、独自のアドレス空間を使用し、プロセス間通信メカニズム(通常はオペレーティングシステムによって管理される)を介してのみ相互作用する独立した実行ユニットです。通常、アプリケーションは設計段階でプロセスに分割され、重要なアプリケーション機能を論理的に分離する意味がある場合、マスタープロセスはサブプロセスを明示的に生成します。言い換えれば、プロセスはアーキテクチャ上の構成要素です。

対照的に、スレッドはアプリケーションのアーキテクチャに影響を与えないコーディング構造です。 1つのプロセスに複数のスレッドが含まれる場合があります。プロセス内のすべてのスレッドは同じ状態と同じメモリ空間を共有し、同じ変数を共有するため、互いに直接通信できます。

ソース

1
Ty.

記事が間違っていると思います。大規模な.NETショップを運営している場合は、単に ThreadPool ドキュメントの1つのステートメントに基づいて、複数のアプリと複数のWebサイトで(個別のアプリプールを使用して)プールを安全に使用できます。

プロセスごとに1つのスレッドプールがあります。スレッドプールのデフォルトサイズは、利用可能なプロセッサーごとに250ワーカースレッド、1000 I/O完了スレッドです。スレッドプール内のスレッドの数は、SetMaxThreadsメソッドを使用して変更できます。各スレッドはデフォルトのスタックサイズを使用し、デフォルトの優先度で実行されます。

1

その記事は正しくありません。 ASP.NETには、ASP.NET要求を処理するための独自のスレッドプール、管理されたワーカースレッドがあります。このプールは通常、数百のスレッドであり、ThreadPoolプールとは異なります。ThreadPoolプールは、より小さなプロセッサーの倍数です。

ASP.NETでThreadPoolを使用しても、ASP.NETワーカースレッドに干渉しません。 ThreadPoolの使用は問題ありません。

また、メッセージを記録し、プロデューサー/コンシューマーパターンを使用してログメッセージをそのスレッドに渡すための単一のスレッドをセットアップすることもできます。その場合、スレッドは長命であるため、ロギングを実行するには単一の新しいスレッドを作成する必要があります。

すべてのメッセージに新しいスレッドを使用するのは間違いなくやり過ぎです。

もう1つの方法は、ログについてのみ話している場合、log4netなどのライブラリを使用することです。別のスレッドでロギングを処理し、そのシナリオで発生する可能性のあるすべてのコンテキストの問題を処理します。

1
Samuel Neff

IISが同じThreadPoolを使用して着信要求を処理するかどうかは、明確な回答を得るのが難しく、バージョン間で変更されているようです。使用しないことをお勧めしますThreadPoolスレッドが多すぎるため、IISで多くのスレッドを使用できます。一方、小さなタスクごとに独自のスレッドを生成することは悪い考えのようです。おそらく、何らかのロックがあります。ロギングでは、一度に1つのスレッドのみが進行し、残りのスレッドは順番にスケジュールされ、スケジュールされなくなります(新しいスレッドを生成するオーバーヘッドは言うまでもありません)本質的に、ThreadPoolが設計された正確な問題に遭遇します避けるために。

合理的な妥協案は、メッセージを渡すことができる単一のロギングスレッドをアプリが割り当てることです。アプリの速度が低下しないように、メッセージの送信は可能な限り高速になるように注意する必要があります。

0
bmm6o

Parallel.ForまたはParallel.ForEachを使用して、スムーズに実行し、プールの枯渇を防ぐために割り当てる可能性のあるスレッドの制限を定義できます。

ただし、バックグラウンドで実行される場合は、ASP.Net Webアプリケーションで以下の純粋なTPLスタイルを使用する必要があります。

var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;

ParallelOptions po = new ParallelOptions();
            po.CancellationToken = ts.Token;
            po.MaxDegreeOfParallelism = 6; //limit here

 Task.Factory.StartNew(()=>
                {                        
                  Parallel.ForEach(collectionList, po, (collectionItem) =>
                  {
                     //Code Here PostLog(logEvent);
                  }
                });
0
khayam

参照されている記事(C#feeds.com)に同意しません。新しいスレッドを作成するのは簡単ですが、危険です。シングルコアで実行するアクティブスレッドの最適数は、実際には10未満と驚くほど少ないです。スレッドがマイナータスク用に作成された場合、マシンがスレッドの切り替えに時間を浪費するのはあまりにも簡単です。スレッドは、管理が必要なリソースです。 WorkItem抽象化は、これを処理するためにあります。

ここでは、リクエストに使用できるスレッドの数を減らすことと、スレッドの数が多すぎてそれらを効率的に処理できないようにすることとの間にトレードオフがあります。これは非常に動的な状況ですが、スレッドの作成より先にとどまるためにプロセッサに任せるのではなく、アクティブに管理する必要があると思います(この場合はスレッドプールによって)。

最後に、この記事ではThreadPoolを使用することの危険性についてかなり大々的に述べていますが、それらをバックアップするには具体的なものが本当に必要です。

0
Timbo