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


111

ASP.NETでも、短期間のバックグラウンドタスク(クリティカルでないとしましょう)にThreadPoolを使用することがベストプラクティスであると常に考えていましたが、そうでない場合はこの記事に出くわしました。引数は、ASP.NET関連の要求を処理するためにThreadPoolを残すべきであるということです。

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

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

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

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

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

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

サイトのトラフィックが多すぎてThreadPoolがいっぱいになっている場合は、帯域外に移行することをお勧めします。または、完全なThreadPoolはとにかくリソースの制限に達していることを意味します。独自のスレッドを開始しようとすべきではありませんか?

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


プロットが厚くなります-この記事(blogs.msdn.com/nicd/archive/2007/04/16/…)を見つけましたが、完全にはデコードできません。一方では、IIS 6.0+は常にスレッドプールワーカースレッドで要求を処理する(そして以前のバージョンで処理する可能性がある)と言っているようですが、次のようになります:「ただし、新しい.NET 2.0非同期ページを使用している場合( Async = "true")またはThreadPool.QueueUserWorkItem()の場合、処理の非同期部分は[完了ポートスレッド]内で行われます。 " 処理の非同期部分
ジェフスターナル、

もう1つ-スレッドプールの使用可能なワーカースレッドがその最大ワーカースレッドよりも低いかどうかを検査し、キュー内で同じことを実行することにより、IIS 6.0以降のインストール(現時点ではありません)でテストするのは簡単です。作業項目。
ジェフスターナル、

回答:


104

ここでの他の答えは最も重要なポイントを省いているようです:

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

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

はい、そうです。あまりにも多くの作業項目をキューに入れることThreadPoolにより、ASP.NETプロセスで不足する可能性があります。ASP.NETがそれ以上のリクエストを処理するのを防ぎます。記事の情報はその点で正確です。と同じスレッドプールがQueueUserWorkItem要求の処理にも使用されます。

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

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

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

これらのメソッドはを使用ますがThreadPool、ASP.NET要求に干渉しない IOCPを使用します。これらは、I / Oシステムからの割り込み信号によって「起動」できる特殊な軽量スレッドです。また、ASP.NETアプリケーションでは、通常、ワーカースレッドごとに1つのI / Oスレッドがあるため、すべての単一の要求で1つの非同期操作をキューに入れることができます。これは文字通り数百の非同期操作であり、パフォーマンスの大幅な低下はありません(I / Oサブシステムが維持できると仮定)それはあなたが今までに必要とするよりもはるかに多いです。

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

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

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


その詳細な応答のおかげで、あなたは正しいです。私は、可能な場合は非同期メソッドを使用するようにしています(ASP.NET MVCの非同期コントローラーと組み合わせて)。私の例の場合、リモートロガーを使用すると、これがまさに私が実行できることです。これは興味深い設計上の問題ですが、非同期処理をコードの最低レベル(つまり、ロガーの実装)まで押し下げ、たとえばコントローラーレベル(後者の場合)から決定することができないためです。 、たとえば、2つのロガー実装から選択できるようにする必要があります)。
マイケルハート

@Michael:非同期コールバックは、より多くのレベルを上げたい場合、一般的にラップするのがかなり簡単です。Action<T>たとえば、非同期メソッドの周囲にファサードを作成し、それらをコールバックとして使用する単一のメソッドでラップすることができます。ワーカースレッドを使用するかI / Oスレッドを使用するかの選択が最低レベルで行われることを意味する場合、それは意図的なものです。そのレベルだけが、IOCPが必要かどうかを判断できます。
アーロノート

興味深い点として、ThreadPoolこの方法で制限するのは.NETだけですが、おそらく開発者が正しいことを信頼していないためです。アンマネージWindowsスレッドプールには非常によく似たAPIがありますが、実際にはスレッドタイプを選択できます。
アーロノート

2
I / O完了ポート(IOCP)。IOCPの説明は正確ではありません。IOCPでは、静的な数のワーカースレッドがあり、保留中のすべてのタスクを順番に処理します。固定または動的なサイズのスレッドプールと混同しないでください。ただし、タスクごとに1つのスレッドがあります。ASYNCとは異なり、タスクごとに1つのスレッドはありません。IOCPスレッドはタスク1で少し動作し、次にタスク3、タスク2に切り替えてから、再びタスク1に戻ることがあります。タスクセッションの状態は保存され、スレッド間で渡されます。
MickyD 2012年

1
データベースの挿入はどうですか?ASYNC SQLコマンド(実行など)はありますか?データベースの挿入は(ロックのため)最も遅いI / O操作であり、行が挿入されるまでメインスレッドを待機させることは、CPUサイクルの無駄遣いにすぎません。
Ian Thompson

45

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

記事から

Q)ASP.NETアプリケーションがCLR ThreadPoolスレッドを使用する場合、CLR ThreadPoolも使用して要求を実行するASP.NETを枯渇させないのですか? ..

A)まとめると、ASP.NETでスレッドが不足することを心配する必要はありません。問題が発生したと思われる場合は、お知らせください。問題を解決します。

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

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

  1. QueueUserWorkItemに比べて非常に高価です...ところで、CLRよりも優れたThreadPoolを記述できる場合は、Microsoftでの仕事に応募することをお勧めします。

4

ウェブサイトはスポーンスレッドを回避するべきではありません。

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

-編集

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

-編集

これがスレッドだけではない理由をさらに詳しく説明します。

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

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


4
この重要な声明が常に真実であることに私は同意しません-特に重要でないタスクの場合。非同期ロギングの目的だけでWindowsサービスを作成することは、明らかにやり過ぎに思われます。さらに、そのオプションは常に利用できるわけではありません(MSMQやWindowsサービスを展開できるため)。
マイケルハート、

もちろん、それはWebサイトから非同期タスクを実装するための「標準」の方法です(他のプロセスに対するキューテーマ)。
正午シルク

2
すべての非同期タスクが同じように作成されるわけではないため、たとえばASP.NETに非同期ページが存在するのはそのためです。リモートWebサービスから結果をフェッチして表示したい場合は、MSMQを介して実行することはしません。この場合、リモートポストを使用してログに書き込みます。Windowsサービスを作成することは問題に適合せず、そのためにMSMQをフックすることもできません(この特定のアプリがAzureにあるため、私も適合できません)。
マイケルハート

1
検討してください:あなたはリモートホストに書いていますか?そのホストがダウンしているか到達できない場合はどうなりますか?書き込みを再試行しますか?たぶんあなたはそうするでしょう、そうでないかもしれません。あなたの実装では、再試行するのは難しいです。このサービスにより、それは非常に簡単になります。私はあなたがそれをすることができないかもしれないことを感謝します、そして私は他の誰かにウェブサイトからのスレッドの作成に関する特定の問題に答えさせます[すなわち、あなたのスレッドがバックグラウンドでなかった場合など]私は「適切な」ことを概説していますそれを行う方法。ec2を使用しましたが、私はazureに詳しくありません(OSをインストールできるので、何でもかまいません)。
正午シルク

@silky、コメントありがとうございます。私は、このより重い(まだ耐久性のある)ソリューションを回避するために「非クリティカル」と言っていました。キューに入れられた作業項目のベストプラクティスを求めていないことを明確にするために、質問を明確にしました。Azureはこのタイプのシナリオをサポートします(独自のキューストレージがあります)。ただし、キューイング操作は同期ロギングには高すぎるため、とにかく非同期ソリューションが必要です。私の場合、失敗の落とし穴を知っていますが、この特定のロギングプロバイダーが失敗した場合に備えて、インフラストラクチャを追加するつもりはありません。他のロギングプロバイダーも持っています。
マイケルハート、

4

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

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

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

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

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


2

QueueUserWorkItemを使用し、ペストを回避するように新しいスレッドを作成しないでください。ASP.NETは同じThreadPoolを使用しているため、ASP.NETが枯渇しない理由を説明するビジュアルについて、ボウリングのピンや剣などを飛行中に2本の手で操作する熟練したジャグラーを想像してください。独自のスレッドの作成が悪い理由を視覚的に示すために、ラッシュアワーでシアトルで何が起きるかを想像してください。高速道路への入り口ランプを頻繁に使用すると、ライトを使用して入り口の数を数秒ごとに1つに制限する代わりに、車両がすぐに通行できます。 。最後に、詳細な説明については、このリンクを参照してください:

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

ありがとう、トーマス


そのリンクは非常に便利です、そのトーマスに感謝します。@Aaronaughtの反応についてのあなたの意見も聞かせてください。
マイケルハート

私はアーロノートに同意し、私のブログ記事で同じことを述べました。「この決定を簡略化するために、何もしないときにASP.NET要求スレッドをブロックする場合にのみ、[別のスレッドに]切り替える必要があります。これは単純化しすぎですが、決定を簡単にするためです。」言い換えれば、ノンブロッキングの計算作業では行わないでください。ただし、リモートサーバーに対して非同期のWebサービス要求を行う場合は行ってください。アーロノートを聞いてください!:)
トーマス

1

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

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

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

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

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


1
@サム、私は実際にlog4netを使用していて、ログが別のスレッドで書き込まれているのを見ていません-有効にする必要があるオプションの種類はありますか?
マイケルハート

1

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

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


単一のプロセスで実行されている1つのアプリケーションは、それ自体を完全に停止させることができます。(または、少なくとも、スレッドプールを失わせる命題にするのに十分なパフォーマンスを低下させます。)
Jeff Sternal

ASP.NETリクエストはI / O完了スレッドを使用していると思います(ワーカースレッドではありません)-正しいですか?
マイケルハート、

フリッツオニオンの記事で私が回答にリンクしました。「このパラダイムは[IIS 5.0からIIS 6.0に変更されました] ASP.NETでのリクエストの処理方法を変更します。リクエストをinetinfo.exeからASP.NETワーカープロセスhttpにディスパッチするのではなく。 sysは各要求を適切なプロセスで直接キューに入れます。したがって、すべての要求は、CLRスレッドプールから取得されたワーカースレッドによって処理され、I / Oスレッドでは処理されません。」(私の強調)
ジェフ・スターナル

うーん、まだ完全に定かではありません...その記事は2003年6月からです。2004年5月からこの記事を読んだ場合(確かにかなり古い)、「ASPを維持するためにSleep.aspxテストページを使用できます。 Sleep.aspxはちょうど眠りに現在実行中のスレッドを起こし.NET I / Oスレッド忙しい」、:msdn.microsoft.com/en-us/library/ms979194.aspx -私はチャンスを持っている場合は、私が表示されますその例をコード化してIIS 7と.NET 3.5でテストできるかどうか
マイケルハート

ええ、その段落のテキストは紛らわしいです。そのセクションのさらに進んで、説明するサポートトピック(support.microsoft.com/default.aspx?scid=kb;EN-US;816829)にリンクしています。I / O完了スレッドでの要求の実行は.NET Framework 1.0でしたASP.NET 1.1 2003年6月の修正プログラムロールアップパッケージで修正された問題(その後、「すべての要求がワーカースレッドで実行される」)。さらに重要なことに、この例では、ASP.NETスレッドプールがによって公開されてSystem.Threading.ThreadPoolいるスレッドプールと同じであることを明確に示しています。
ジェフスターナル

1

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

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

リモートロギングの例を示しますが、それはロガーの懸念事項です。メッセージをタイムリーに受信するには、非同期プロセスを用意する必要があります。Samは、ロガー(log4net)がすでにこれをサポートしているはずだとさえ指摘しています。

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

スレッドとプロセス

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

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

ソース


3
@Ty、入力に感謝しますが、私はWebサーバーがどのように機能するかをよく知っており、それは質問にはあまり関係ありません問題。特定の技術情報を求めています。すでに非同期プロセスが設定されている必要があるのは「ロガーの懸念」であるため、非同期プロセスはロガー実装によってどのように記述されるべきだと思いますか?
マイケルハート、

0

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

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

最後に、この記事ではThreadPoolを使用することの危険性についてかなり抜本的な説明をしていますが、実際にはそれらをバックアップするための具体的なものが必要です。


0

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

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


0

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);
                  }
                });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.