マネージ スレッド プール
更新 : 2007 年 11 月
ThreadPool クラスを使用すると、システムによって管理されるワーカー スレッドのプールがアプリケーションに提供されます。これによって、スレッド管理ではなくアプリケーション タスクに集中できるようになります。バックグラウンド処理が必要な短いタスクがある場合、マネージ スレッド プールを使用すると、複数のスレッドを簡単に利用できます。
メモ : |
---|
.NET Framework version 2.0 Service Pack 1 以降では、.NET Framework の以前のリリースでボトルネックとして識別されていた 3 つの重要な部分 (キューへのタスクの配置、スレッド プールのスレッドのディスパッチ、および I/O 完了スレッドのディスパッチ) において、スレッド プールのスループットが大幅に向上しています。この機能を使用するには、アプリケーションで .NET Framework version 3.5 を対象とする必要があります。詳細については、「.NET Framework 3.5 のアーキテクチャ」を参照してください。 |
ユーザー インターフェイスと対話するバックグラウンド タスクの場合、.NET Framework Version 2.0 では、ユーザー インターフェイス スレッドで生成されたイベントを使用して通信する BackgroundWorker クラスも利用できます。
.NET Framework では、非同期 I/O 完了、タイマ コールバック、登録済みの待機操作、デリゲートを使用した非同期メソッド呼び出し、System.Net ソケット接続などのさまざまな目的でスレッド プールのスレッドを使用します。
スレッド プールのスレッドを使用しない場合
次のような場合は、スレッド プールのスレッドを使用する代わりに独自のスレッドを作成して管理する方が適切です。
フォアグラウンド スレッドが必要な場合。
スレッドに特定の優先順位を設定する必要がある場合。
スレッドを長時間にわたってブロックするタスクがある場合。スレッド プールにはスレッドの最大数が存在するため、多数のスレッド プール スレッドがブロックされると、タスクを開始できなくなることがあります。
スレッドをシングルスレッド アパートメントに配置する必要がある場合。ThreadPool スレッドはすべてマルチスレッド アパートメントにあります。
スレッドに関連付けられた、確立された ID が必要な場合、またはスレッドを特定のタスク専用にする必要がある場合。
スレッド プールの特徴
スレッド プールのスレッドはバックグラウンド スレッドです。フォアグラウンド スレッドとバックグラウンド スレッド を参照してください。各スレッドは、既定のスタック サイズを使用して既定の優先順位で実行します。また、スレッドはマルチスレッド アパートメント内にあります。
各プロセスにはスレッド プールが 1 つだけあります。
スレッド プールのスレッドでの例外
スレッド プールのスレッドで処理できない例外が発生すると、プロセスが終了します。この規則には、次の 3 つの例外があります。
Abort が呼び出されたため、スレッド プールのスレッドに ThreadAbortException がスローされる。
アプリケーション ドメインがアンロードされるため、スレッド プールのスレッドに AppDomainUnloadedException がスローされる。
共通言語ランタイムまたはホスト プロセスがスレッドを終了する。
詳細については、「マネージ スレッドの例外」を参照してください。
メモ : |
---|
.NET Framework Version 1.0 および 1.1 では、スレッド プールのスレッドの未処理の例外は、共通言語ランタイムによって通知なしにトラップされます。このため、アプリケーション状態が破損し、最終的にアプリケーションが停止することにより、デバッグが困難になることがあります。 |
スレッド プールのスレッドの最大数
スレッド プールのキューに配置できる操作の数は、使用できるメモリの容量によってしか制限されませんが、スレッド プールでは、プロセスで同時にアクティブにできるスレッドの数が制限されます。既定では、ワーカー スレッドが CPU ごとに 25、I/O 完了スレッドが 1,000 に制限されています。
スレッドの最大数を制御するには、GetMaxThreads メソッドと SetMaxThreads メソッドを使用します。
メモ : |
---|
.NET Framework Version 1.0 および 1.1 では、スレッド プールのサイズはマネージ コードから設定できません。共通言語ランタイムをホストするコードでは、mscoree.h で定義された CorSetMaxThreads を使用してサイズを設定できます。 |
アイドル スレッドの最小数
スレッド プールは、すべてのスレッドがアイドルの場合でも最小数の空きスレッドを保持するため、キューに配置されたタスクをすぐに開始できます。この下限を超えたアイドル スレッドは、システム リソースを節約するために終了されます。既定では、プロセッサごとに 1 つのアイドル スレッドが保持されます。
スレッド プールには、新しいアイドル スレッドを開始するまでの遅延時間 (.NET Framework Version 2.0 では 0.5 秒) が組み込まれています。アプリケーションが短時間のうちに多くのタスクを定期的に開始する場合、アイドル スレッドの数をわずかに増やすだけで、スループットを大幅に向上させることができます。アイドル スレッドの数を多く設定しすぎると、システム リソースが無駄に消費されます。
スレッド プールで保持されるアイドル スレッドの数を制御するには、GetMinThreads メソッドと SetMinThreads メソッドを使用します。
メモ : |
---|
.NET Framework Version 1.0 では、アイドル スレッドの最小数を設定できません。 |
セキュリティ チェックのスキップ
スレッド プールでは、ThreadPool.UnsafeQueueUserWorkItem メソッドと ThreadPool.UnsafeRegisterWaitForSingleObject メソッドも使用できます。これらのメソッドは、呼び出し元のスタックが、キューに配置されているタスクの実行時に行われるセキュリティ チェックと無関係であることが確認できる場合にのみ使用します。QueueUserWorkItem と RegisterWaitForSingleObject は共に呼び出し元のスタックを取り込みます。このスタックは、スレッドがタスクの実行を開始するときにスレッド プールのスレッドのスタックにマージされます。セキュリティ チェックが必要な場合は、そのスタック全体をチェックする必要があります。チェックは安全性を提供しますが、パフォーマンスへの影響もあります。
スレッド プールの使用
スレッド プールを使用するには、マネージ コードから ThreadPool.QueueUserWorkItem (またはアンマネージ コードから CorQueueUserWorkItem) を呼び出し、タスクを実行するメソッドを表す WaitCallback デリゲートを渡します。また、待機操作の関連ワーク アイテムをキューに置くこともできます。これは、ThreadPool.RegisterWaitForSingleObject メソッドを使用し、WaitOrTimerCallback デリゲートによって表されるメソッドへの呼び出しを発生させる WaitHandle を、シグナル状態になるときまたはタイムアウトされるときに渡すことによって実行します。どちらの場合も、スレッド プールはバックグラウンド スレッドを使用してコールバック メソッドを呼び出します。
ThreadPool の例
QueueUserWorkItem メソッドと RegisterWaitForSingleObject メソッドを使用する 3 つのコード例を次に示します。
最初の例では、ThreadProc メソッドで表される単純なタスクを QueueUserWorkItem メソッドを使用してキューに置きます。
Imports System
Imports System.Threading
Public Class Example
Public Shared Sub Main()
' Queue the task.
ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc))
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the thread pool task runs. The thread pool uses background
' threads, which do not keep the application running. (This
' is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
End Sub
' This thread procedure performs the task.
Shared Sub ThreadProc(stateInfo As Object)
' No state object was passed to QueueUserWorkItem, so
' stateInfo is null.
Console.WriteLine("Hello from the thread pool.")
End Sub
End Class
using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
QueueUserWorkItem へのタスク データの供給
QueueUserWorkItem を使用してタスクをキューに置き、そのタスクにデータを与えるコード例を次に示します。
Imports System
Imports System.Threading
' TaskInfo holds state information for a task that will be
' executed by a ThreadPool thread.
Public Class TaskInfo
' State information for the task. These members
' can be implemented as read-only properties, read/write
' properties with validation, and so on, as required.
Public Boilerplate As String
Public Value As Integer
' Public constructor provides an easy way to supply all
' the information needed for the task.
Public Sub New(text As String, number As Integer)
Boilerplate = text
Value = number
End Sub
End Class
Public Class Example
Public Shared Sub Main()
' Create an object containing the information needed
' for the task.
Dim ti As New TaskInfo("This report displays the number {0}.", 42)
' Queue the task and data.
If ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf ThreadProc), ti) Then
Console.WriteLine("Main thread does some work, then sleeps.")
' If you comment out the Sleep, the main thread exits before
' the ThreadPool task has a chance to run. ThreadPool uses
' background threads, which do not keep the application
' running. (This is a simple example of a race condition.)
Thread.Sleep(1000)
Console.WriteLine("Main thread exits.")
Else
Console.WriteLine("Unable to queue ThreadPool request.")
End If
End Sub
' The thread procedure performs the independent task, in this case
' formatting and printing a very simple report.
'
Shared Sub ThreadProc(stateInfo As Object)
Dim ti As TaskInfo = CType(stateInfo, TaskInfo)
Console.WriteLine(ti.Boilerplate, ti.Value)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
// State information for the task. These members
// can be implemented as read-only properties, read/write
// properties with validation, and so on, as required.
public string Boilerplate;
public int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
public TaskInfo(string text, int number) {
Boilerplate = text;
Value = number;
}
}
public class Example {
public static void Main() {
// Create an object containing the information needed
// for the task.
TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the ThreadPool task has a chance to run. ThreadPool uses
// background threads, which do not keep the application
// running. (This is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
else {
Console.WriteLine("Unable to queue ThreadPool request.");
}
}
// The thread procedure performs the independent task, in this case
// formatting and printing a very simple report.
//
static void ThreadProc(Object stateInfo) {
TaskInfo ti = (TaskInfo) stateInfo;
Console.WriteLine(ti.Boilerplate, ti.Value);
}
}
RegisterWaitForSingleObject
スレッド処理の次の機能を説明する例を以下に示します。
ThreadPool スレッドで実行するタスクを、RegisterWaitForSingleObject メソッドを使用してキューに配置します。
AutoResetEvent を使用して、タスクに実行を指示します。EventWaitHandle、AutoResetEvent、および ManualResetEvent を参照してください。
WaitOrTimerCallback デリゲートを使用して、タイムアウトと通知の両方を処理します。
RegisteredWaitHandle を使用して、キューに置かれたタスクをキャンセルします。
Imports System
Imports System.Threading
' TaskInfo contains data that will be passed to the callback
' method.
Public Class TaskInfo
public Handle As RegisteredWaitHandle = Nothing
public OtherInfo As String = "default"
End Class
Public Class Example
Public Shared Sub Main()
' The main thread uses AutoResetEvent to signal the
' registered wait handle, which executes the callback
' method.
Dim ev As New AutoResetEvent(false)
Dim ti As New TaskInfo()
ti.OtherInfo = "First task"
' The TaskInfo for the task includes the registered wait
' handle returned by RegisterWaitForSingleObject. This
' allows the wait to be terminated when the object has
' been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject( _
ev, _
New WaitOrTimerCallback(AddressOf WaitProc), _
ti, _
1000, _
false _
)
' The main thread waits about three seconds, to demonstrate
' the time-outs on the queued task, and then signals.
Thread.Sleep(3100)
Console.WriteLine("Main thread signals.")
ev.Set()
' The main thread sleeps, which should give the callback
' method time to execute. If you comment out this line, the
' program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000)
' If you start a thread yourself, you can wait for it to end
' by calling Thread.Join. This option is not available with
' thread pool threads.
End Sub
' The callback method executes when the registered wait times out,
' or when the WaitHandle (in this case AutoResetEvent) is signaled.
' WaitProc unregisters the WaitHandle the first time the event is
' signaled.
Public Shared Sub WaitProc(state As Object, timedOut As Boolean)
' The state object must be cast to the correct type, because the
' signature of the WaitOrTimerCallback delegate specifies type
' Object.
Dim ti As TaskInfo = CType(state, TaskInfo)
Dim cause As String = "TIMED OUT"
If Not timedOut Then
cause = "SIGNALED"
' If the callback method executes because the WaitHandle is
' signaled, stop future execution of the callback method
' by unregistering the WaitHandle.
If Not ti.Handle Is Nothing Then
ti.Handle.Unregister(Nothing)
End If
End If
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", _
ti.OtherInfo, _
Thread.CurrentThread.GetHashCode().ToString(), _
cause _
)
End Sub
End Class
using System;
using System.Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public class Example {
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
1000,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}