スレッド プーリング
アプリケーションによっては、スレッド プーリングを使用すると、複数のスレッドをより効率的に使用できます。アプリケーションは複数のスレッドを使うことが多いのですが、スレッドはイベントが発生するまでの長い時間、スリープ状態になることがしばしばあります。また、変更をポーリングしたり、状態情報を更新したりするために定期的に起動されるだけで、それ以外はスリープ状態にあるスレッドもあります。スレッド プーリングを使用すると、システムによって管理されるワーカー スレッドのプールがアプリケーションに提供されます。これによって、スレッド管理ではなくアプリケーション タスクに集中できるようになります。実際に、複数のスレッドを必要とする短いタスクが多数ある場合、複数のスレッドを活用する最も簡単で最適な方法は、ThreadPool クラスを使用することです。スレッド プールを使用すると、複数のスレッドの使用を最適化してこのプロセスのスループットを向上できます。さらに、アプリケーションには関連情報の提供されないコンピュータ上の、ほかのプロセスのスループットも向上させることができます。スレッド プールを使用すると、システムはコンピュータ上の現在のプロセスをすべて考慮して、スレッドのタイム スライスを最適化できます。
.NET Framework は、スレッド プーリングを、非同期呼び出し、System .Net ソケット接続、非同期 I/O 完了、タイマ、登録済みの待機操作などの目的に使用します。
スレッド プールを使用するには、マネージ コードから ThreadPool.QueueUserWorkItem を呼び出すか、またはアンマネージ コードから CorQueueUserWorkItem を呼び出し、キューに追加するメソッドをラップする WaitCallback デリゲートを渡します。また、待機操作の関連ワーク アイテムをスレッド プールのキューに置くこともできます。これは、ThreadPool.RegisterWaitForSingleObject を使用して、WaitOrTimerCallback デリゲートによってラップされたメソッドへの呼び出しを発生させる WaitHandle をシグナル時またはタイムアウト時に渡すことによって実行します。どちらの場合も、スレッド プールはバックグラウンド スレッドを使用するか作成してコールバック メソッドを呼び出します。
また、呼び出し元のスタックが、キューに置かれたタスクの実行中に行われるすべてのセキュリティ チェックと無関係である場合は、安全でないメソッド ThreadPool.UnsafeQueueUserWorkItem と ThreadPool.UnsafeRegisterWaitForSingleObject も使用できます。QueueUserWorkItem と RegisterWaitForSingleObject は、どちらも呼び出し元のスタックをキャプチャします。このスタックは、スレッド プールのスレッドがタスクの実行を開始したときに、そのスレッドのスタックに結合されます。セキュリティ チェックが必要な場合は、そのスタック全体をチェックする必要があります。チェックは安全性を提供しますが、パフォーマンスへの影響もあります。安全でないメソッドを呼び出すと、完全な安全性は提供されませんが、パフォーマンスは向上します。
各プロセスには ThreadPool オブジェクトが 1 つだけあります。スレッド プールは、ThreadPool.QueueUserWorkItem を最初に呼び出したときに作成されるか、タイマまたは登録済み待機操作がコールバック メソッドをキューに置くと作成されます。1 つのスレッドが、スレッド プールのキューに置かれたすべてのタスクを監視します。タスクが完了すると、スレッド プールからのスレッドが、対応するコールバック メソッドを実行します。ワーク アイテムは、キューに置いた後はキャンセルできません。
スレッド プールのキューに置くことができる操作の数を制限するのは、使用できるメモリの量だけです。ただしスレッド プールは、CPU の数やほかの考慮事項によっては、プロセスで同時にアクティブにできるスレッド数の制限を強制的に施行します。各スレッドは、既定のスタック サイズを使用して既定の優先順位で実行します。また、スレッドはマルチスレッド アパートメント内にあります。スレッドの 1 つがマネージ コード内でイベント待機中などのアイドル状態になると、スレッド プールは別のワーカー スレッドを挿入して、すべてのプロセッサをビジー状態のままにします。スレッド プールのすべてのスレッドが常にビジー状態であり、キューに保留中のワークがある場合、スレッド プールは、一定の期間後に別のワーカー スレッドを作成します。ただし、スレッド数が最大値を超えることはありません。また、ThreadPool は、ThreadPool コールバックを実行するときに、正しい AppDomain に切り替えます。
ThreadPool を使用するのではなく、独自のスレッドを作成および管理する方が適切な場合もあります。独自のスレッドを作成および管理する必要がある場合を次に示します。
- タスクに特定の優先順位を設定する必要がある場合。
- タスクが長時間実行して、ほかのタスクをブロックする可能性がある場合。
- スレッドをシングルスレッド アパートメント内に配置する必要がある場合 (すべての ThreadPool スレッドがマルチスレッド アパートメント内にある場合)。
- スレッドに関連付けられている確立された ID が必要な場合。たとえば、そのスレッドをアボートまたは中断したり、名前で探索したりするために、専用のスレッドを使用する場合があります。
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
[C#]
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
[C#]
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
スレッド処理の次の機能を説明する例を以下に示します。
- RegisterWaitForSingleObject を使用して、ThreadPool スレッドによって実行されるタスクをキューに置きます。
- AutoResetEvent を使用してタスクに実行を指示します。
- 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
[C#]
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
);
}
}
参照
スレッド処理オブジェクトと機能 | ThreadPool クラス | スレッドおよびスレッド処理 | 非同期 I/O 完了 | タイマ