Managed 執行緒集區
更新:2007 年 11 月
ThreadPool 類別為您的應用程式提供了受到系統管理的背景工作執行緒 (Worker Thread) 集區,讓您專注於應用程式工作上,而不是執行緒的管理。如果您有需要在背景處理的簡短工作,Managed 執行緒集區是利用多個執行緒的一個簡單方式。
注意事項: |
---|
從 .NET Framework 2.0 版 Service Pack 1 開始,執行緒集區的處理量在三個關鍵領域有了重大改進:佇列工作、分派執行緒集區的執行緒以及分派 I/O 完成執行緒,這三個領域在舊版的 .NET Framework 中,被視為是瓶頸所在。若要使用此功能,您的應用程式應將目標設為 .NET Framework 3.5 版。如需詳細資訊,請參閱 .NET Framework 3.5 架構。 |
對於與使用者介面互動的背景工作而言,.NET Framework 2.0 版也提供了 BackgroundWorker 類別,此類別可使用使用者介面執行緒上引發的事件來溝通。
.NET Framework 使用執行緒集區執行緒的目的有許多個,其中包括非同步的 I/O 完成、計時器回呼、登錄的等候作業、使用委派的非同步方法,以及 System.Net 通訊端連接。
何時不該使用執行緒集區執行緒
有好幾種情況適合建立及管理自己的執行緒,而不須使用執行緒集區執行緒:
您需要前景執行緒。
您需要執行緒有特定的優先權。
您有一些工作會造成執行緒封鎖一段很長的時間。執行緒集區有執行緒數目的上限,所以大量已封鎖的執行緒集區執行緒可能會讓工作無法啟動。
您需要將執行緒放在單一執行緒 Apartment 中;所有 ThreadPool 執行緒都在多執行緒 Apartment 中。
您需要有一個與執行緒有關的穩定識別,或是讓執行緒專屬於某個工作。
執行緒集區特性
執行緒集區執行緒為背景執行緒。請參閱前景和背景執行緒。每個執行緒均使用預設的堆疊大小、以預先優先權執行,並且位於多執行緒 Apartment 中。
每個處理序只有一個執行緒集區。
執行緒集區執行緒中的例外狀況
執行緒集區執行緒上未處理的例外狀況會結束處理序。此項規則有三個例外情形:
執行緒集區執行緒中會擲回 ThreadAbortException,因為已呼叫 Abort。
執行緒集區執行緒中會擲回 AppDomainUnloadedException,因為正在卸載應用程式定義域。
Common Language Runtime 或主應用程式處理序會結束此執行緒。
如需詳細資訊,請參閱 Managed 執行緒中的例外狀況。
注意事項: |
---|
在 .NET Framework 1.0 和 1.1 版中,Common Language Runtime 會以無訊息模式在執行緒集區執行緒中截獲未處理的例外狀況。如此一來,可能會損壞應用程式狀態,而最後導致應用程式無回應,這樣可能會讓偵錯工作變得相當困難。 |
執行緒集區執行緒數目的最大值
可以排入到執行緒集區佇列中的作業數目只受限於可用記憶體;但是,執行緒集區會限制可同時在處理序中使用的執行緒數目。根據預設,此限制為每個 CPU 25 個背景工作執行緒,以及 1,000 個 I/O 完成執行緒。
您可以使用 GetMaxThreads 和 SetMaxThreads 方法來控制執行緒數目的最大值。
注意事項: |
---|
在 .NET Framework 1.0 和 1.1 版中,不能從 Managed 程式碼設定執行緒集區的大小。裝載 Common Language Runtime 的程式碼可以使用 mscoree.h 中定義的 CorSetMaxThreads 來設定大小。 |
閒置執行緒數目的最小值
執行緒集區也可維護可用執行緒數目的最小值,甚至在所有執行緒都閒置時,好讓排入佇列的工作可以立即啟動。超過此最小值的閒置執行緒會被結束,以節省系統資源。根據預設,每個處理器會維護一個閒置執行緒。
在啟動新的閒置執行緒之前,執行緒集區會有內建的延遲 (.NET Framework 2.0 版中為半秒鐘)。如果應用程式定期會在很短的時間內啟動許多工作,則少量增加閒置執行緒數目可能會讓產量明顯地增加。如果將閒置執行緒數目設定得太高,則會不必要地耗用系統資源。
您可以藉由使用 GetMinThreads 和 SetMinThreads 方法來控制執行緒集區所維護的閒置執行緒數目。
注意事項: |
---|
在 .NET Framework 1.0 版中,無法設定閒置執行緒數目的最小值。 |
略過安全性檢查
執行緒集區也提供 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject 方法。只有當您確定呼叫端的堆疊與排入佇列之工作的執行期間所做的任何安全性檢查無關時,才可使用這些方法。QueueUserWorkItem 和 RegisterWaitForSingleObject 都可擷取呼叫端的堆疊,而當執行緒開始執行工作時,此堆疊會合併到執行緒集區執行緒的堆疊內。如果需要安全性檢查,則必須檢查整個堆疊。雖然檢查能夠提供安全性,但是也帶來效能成本。
使用執行緒集區
使用執行緒集區的方式如下:從 Managed 程式碼呼叫 ThreadPool.QueueUserWorkItem (從 Unmanaged 程式碼則為 CorQueueUserWorkItem),並傳遞 WaitCallback 委派,其表示執行此工作的方法。您也可使用 ThreadPool.RegisterWaitForSingleObject 方法,以及傳遞 WaitHandle (此控制代碼在收到信號或逾時的時候,會引發對 WaitOrTimerCallback 委派表示之方法的呼叫),藉以將與等候作業有關的工作項目排入佇列。在這兩種情況下,執行緒集區會使用背景執行緒來叫用回呼方法。
ThreadPool 範例
下列三個程式碼範例將示範 QueueUserWorkItem 和 RegisterWaitForSingleObject 方法。
第一個範例會使用 QueueUserWorkItem 方法查詢一個非常簡單的工作,此工作是由 ThreadProc 方法表示。
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
下列範例示範幾個執行緒處理的功能。
使用 RegisterWaitForSingleObject 方法,將工作排入佇列以供 ThreadPool 執行緒執行。
使用 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
);
}
}