Managed 執行緒集區
更新:2010 年 9 月
ThreadPool 類別為您的應用程式提供了受到系統管理的背景工作執行緒 (Worker Thread) 集區,讓您專注於應用程式工作上,而不是執行緒的管理。 如果您有需要在背景處理的簡短工作,Managed 執行緒集區是利用多個執行緒的一個簡單方式。 例如,從 .NET Framework 4 版開始,您可以建立 Task 和 Task<TResult> 物件,這兩個物件會在執行緒集區的執行緒上執行非同步工作。
注意事項 |
---|
從 .NET Framework 2.0 版 Service Pack 1 開始,執行緒集區的處理量在三個關鍵領域有了重大改進:佇列工作、分派執行緒集區的執行緒以及分派 I/O 完成執行緒,這三個領域在舊版的 .NET Framework 中,被視為是瓶頸所在。若要使用此功能,您的應用程式應將目標設為 .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 會以無訊息模式在執行緒集區執行緒中截獲未處理的例外狀況。如此一來,可能會損壞應用程式狀態,而最後導致應用程式無回應,這樣可能會讓偵錯工作變得相當困難。 |
執行緒集區執行緒數目的最大值
可以排入到執行緒集區佇列中的作業數目只受限於可用記憶體;但是,執行緒集區會限制可同時在處理序中使用的執行緒數目。 從 .NET Framework 4 版開始,處理序的執行緒集區預設大小取決於數個因素,例如虛擬位址空間的大小。 處理序可以呼叫 GetMaxThreads 方法來決定執行緒的數目。
您可以使用 GetMaxThreads 和 SetMaxThreads 方法來控制執行緒數目的最大值。
注意事項 |
---|
在 .NET Framework 1.0 和 1.1 版中,不能從 Managed 程式碼設定執行緒集區的大小。裝載 Common Language Runtime 的程式碼可以使用 mscoree.h 中定義的 CorSetMaxThreads 來設定大小。 |
執行緒集區最小值
執行緒集區會視需要提供新的背景工作執行緒或 I/O 完成執行緒,直到達到每個分類的指定最小值為止。 您可以使用 GetMinThreads 方法取得這些最小值。
注意事項 |
---|
當需求低時,執行緒集區的實際執行緒數目可能低於最小值。 |
當達到最小值時,執行緒即區可以建立額外的執行緒,或是等候部分工作完成。 從 .NET Framework 4 開始,執行緒集區會建立並終結背景工作執行緒,以便最佳化處理量,處理量的定義為每個時間單位完成的工作數。 執行緒太少可能無法將可用資源做最佳的運用,而執行緒太多則可能使資源爭用情況增加。
警告 |
---|
您可以使用 SetMinThreads 方法增加最小閒置執行緒數目。不過,不必要地增加這些值,可能會造成效能問題。如果太多工作同時啟動,則所有工作可能都會變慢。在大部分情況下,執行緒集區採用自己的演算法配置執行緒的效能較佳。 |
略過安全性檢查
執行緒集區也提供 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject 方法。 只有當您確定呼叫端的堆疊與排入佇列之工作的執行期間所做的任何安全性檢查無關時,才可使用這些方法。 QueueUserWorkItem和 RegisterWaitForSingleObject 都可擷取呼叫端的堆疊,而當執行緒開始執行工作時,此堆疊會合併到執行緒集區執行緒的堆疊內。 如果需要安全性檢查,則必須檢查整個堆疊。 雖然檢查能夠提供安全性,但是也帶來效能成本。
使用執行緒集區
從 .NET Framework 4 開始,使用執行緒集區的最簡單方式是使用工作平行程式庫。 根據預設,平行程式庫型別 (像是 Task 和 Task<TResult>) 會使用執行緒集區的執行緒執行工作。 您也可以透過下列方式使用執行緒集區:從 Managed 程式碼呼叫 ThreadPool.QueueUserWorkItem (從 Unmanaged 程式碼則為 CorQueueUserWorkItem),並傳遞 WaitCallback 委派,表示執行此工作的方法。 使用執行緒集區的另一種方式,是使用 ThreadPool.RegisterWaitForSingleObject 方法並傳遞 WaitHandle (它在收到信號或逾時的時候會呼叫 WaitOrTimerCallback 委派表示的方法),藉以將與等候作業有關的工作項目排入佇列。 執行緒集區的執行緒可用來叫用回呼方法。
ThreadPool 範例
本節中的程式碼範例會使用 Task 類別、ThreadPool.QueueUserWorkItem 方法和 ThreadPool.RegisterWaitForSingleObject 方法示範執行緒集區。
使用工作平行程式庫執行非同步工作
使用 QueueUserWorkItem 以非同步方式執行程式碼
提供 QueueUserWorkItem 的工作資料
使用 RegisterWaitForSingleObject
使用工作平行程式庫執行非同步工作
下列範例會示範如何透過呼叫 TaskFactory.StartNew 方法建立和使用 Task 物件。 如需使用 Task<TResult> 類別從非同步工作傳回值的範例,請參閱 HOW TO:傳回工作的值。
Imports System.Threading
Imports System.Threading.Tasks
Module StartNewDemo
' Demonstrated features:
' Task ctor()
' Task.Factory
' Task.Wait()
' Task.RunSynchronously()
' Expected results:
' Task t1 (alpha) is created unstarted.
' Task t2 (beta) is created started.
' Task t1's (alpha) start is held until after t2 (beta) is started.
' Both tasks t1 (alpha) and t2 (beta) are potentially executed on threads other than the main thread on multi-core machines.
' Task t3 (gamma) is executed synchronously on the main thread.
' Documentation:
' https://msdn.microsoft.com/en-us/library/system.threading.tasks.task_members(VS.100).aspx
Private Sub Main()
Dim action As Action(Of Object) = Sub(obj As Object)
Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId)
End Sub
' Construct an unstarted task
Dim t1 As New Task(action, "alpha")
' Cosntruct a started task
Dim t2 As Task = Task.Factory.StartNew(action, "beta")
' Block the main thread to demonstate that t2 is executing
t2.Wait()
' Launch t1
t1.Start()
Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId)
' Wait for the task to finish.
' You may optionally provide a timeout interval or a cancellation token
' to mitigate situations when the task takes too long to finish.
t1.Wait()
' Construct an unstarted task
Dim t3 As New Task(action, "gamma")
' Run it synchronously
t3.RunSynchronously()
' Although the task was run synchrounously, it is a good practice to wait for it which observes for
' exceptions potentially thrown by that task.
t3.Wait()
End Sub
End Module
using System;
using System.Threading;
using System.Threading.Tasks;
class StartNewDemo
{
// Demonstrated features:
// Task ctor()
// Task.Factory
// Task.Wait()
// Task.RunSynchronously()
// Expected results:
// Task t1 (alpha) is created unstarted.
// Task t2 (beta) is created started.
// Task t1's (alpha) start is held until after t2 (beta) is started.
// Both tasks t1 (alpha) and t2 (beta) are potentially executed on threads other than the main thread on multi-core machines.
// Task t3 (gamma) is executed synchronously on the main thread.
// Documentation:
// https://msdn.microsoft.com/en-us/library/system.threading.tasks.task_members(VS.100).aspx
static void Main()
{
Action<object> action = (object obj) =>
{
Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
};
// Construct an unstarted task
Task t1 = new Task(action, "alpha");
// Cosntruct a started task
Task t2 = Task.Factory.StartNew(action, "beta");
// Block the main thread to demonstate that t2 is executing
t2.Wait();
// Launch t1
t1.Start();
Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);
// Wait for the task to finish.
// You may optionally provide a timeout interval or a cancellation token
// to mitigate situations when the task takes too long to finish.
t1.Wait();
// Construct an unstarted task
Task t3 = new Task(action, "gamma");
// Run it synchronously
t3.RunSynchronously();
// Although the task was run synchrounously, it is a good practice to wait for it which observes for
// exceptions potentially thrown by that task.
t3.Wait();
}
}
使用 QueueUserWorkItem 以非同步方式執行程式碼
下列範例會使用 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.");
}
}
using namespace System;
using namespace System::Threading;
public ref class Example
{
public:
static void Main()
{
// Queue the task.
ThreadPool::QueueUserWorkItem(gcnew 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.");
}
};
int main()
{
Example::Main();
}
提供 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);
}
}
using namespace System;
using namespace System::Threading;
// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public ref 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;
int Value;
// Public constructor provides an easy way to supply all
// the information needed for the task.
TaskInfo(String^ text, int number)
{
Boilerplate = text;
Value = number;
}
};
public ref class Example
{
public:
static void Main()
{
// Create an object containing the information needed
// for the task.
TaskInfo^ ti = gcnew TaskInfo("This report displays the number {0}.", 42);
// Queue the task and data.
if (ThreadPool::QueueUserWorkItem(gcnew 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);
}
};
int main()
{
Example::Main();
}
使用 RegisterWaitForSingleObject
下列範例示範幾個執行緒處理的功能。
使用 RegisterWaitForSingleObject 方法,將工作排入佇列以供 ThreadPool 執行緒執行。
使用 AutoResetEvent,向要執行的工作發出信號。 請參閱 EventWaitHandle、AutoResetEvent、CountdownEvent 和 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
);
}
}
using namespace System;
using namespace System::Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public ref class TaskInfo
{
public:
static RegisteredWaitHandle^ Handle = nullptr;
static String^ OtherInfo = "default";
};
public ref class Example
{
public:
static void Main()
{
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent^ ev = gcnew AutoResetEvent(false);
TaskInfo^ ti = gcnew 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,
gcnew 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.
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 != nullptr)
ti->Handle->Unregister(nullptr);
}
Console::WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti->OtherInfo,
Thread::CurrentThread->GetHashCode().ToString(),
cause
);
}
};
int main()
{
Example::Main();
}
請參閱
工作
參考
概念
其他資源
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2010 年 9 月 |
已更正過時的預設大小以及有關建立新執行緒的過時資訊。 已加入工作平行程式庫的範例。 |
內容 Bug 修正。 |