托管线程池
更新:2007 年 11 月
ThreadPool 类为应用程序提供一个由系统管理的辅助线程池,从而使您可以集中精力于应用程序任务而不是线程管理。如果您具有需要后台处理的短期任务,则托管线程池是可以利用多个线程的便捷方式。
说明: |
---|
从 .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 完成、Timer 回调、注册的等待操作、使用委托的异步方法调用以及 System.Net 套接字连接。
何时不使用线程池线程
在以下几种情况下,适合于创建并管理自己的线程而不是使用线程池线程:
需要前台线程。
需要使线程具有特定的优先级。
您的任务会导致线程长时间被阻塞。由于线程池具有最大线程数限制,因此大量阻塞的线程池线程可能会阻止任务启动。
需要将线程放入单线程单元。所有 ThreadPool 线程均处于多线程单元中。
您需要具有与线程关联的稳定标识,或使某一线程专用于某一任务。
线程池特征
线程池线程是后台线程。请参见前台和后台线程。每个线程都使用默认堆栈大小,以默认的优先级运行,并处于多线程单元中。
每个进程只有一个线程池对象。
线程池线程中的异常
线程池线程中未处理的异常将终止进程。以下为此规则的三种例外情况:
由于调用了 Abort,线程池线程中将引发 ThreadAbortException。
由于正在卸载应用程序域,线程池线程中将引发 AppDomainUnloadedException。
公共语言运行库或宿主进程将终止线程。
有关更多信息,请参见托管线程中的异常。
说明: |
---|
在 .NET Framework 1.0 和 1.1 版中,公共语言运行库将捕获线程池线程中的未处理异常,而不出现任何提示。这可能会破坏应用程序状态,并最终导致应用程序挂起,将很难进行调试。 |
最大线程池线程数
可排队到线程池的操作数仅受可用内存的限制;但是,线程池限制进程中可以同时处于活动状态的线程数。默认情况下,限制每个 CPU 可以使用 25 个辅助线程和 1,000 个 I/O 完成线程。
通过使用 GetMaxThreads 和 SetMaxThreads 方法可以控制最大线程数。
说明: |
---|
在 .NET Framework 1.0 和 1.1 版中,不能从托管代码中设置线程池大小。承载公共语言运行库的代码可以使用 mscoree.h 中定义的 CorSetMaxThreads 设置该大小。 |
最小空闲线程数
即使是在所有线程都处于空闲状态时,线程池也会维持最小的可用线程数,以便队列任务可以立即启动。将终止超过此最小数目的空闲线程,以节省系统资源。默认情况下,每个处理器维持一个空闲线程。
在启动新的空闲线程之前,线程池具有一个内置延迟(在 .NET Framework 2.0 版中为半秒钟)。应用程序在短期内定期启动许多任务时,空闲线程数的微小增加会导致吞吐量显著增加。将空闲线程数设置得过高会浪费系统资源。
使用 GetMinThreads 和 SetMinThreads 方法可以控制线程池所维持的空闲线程数。
说明: |
---|
在 .NET Framework 1.0 版中,不能设置最小空闲线程数。 |
跳过安全检查
线程池还提供了 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject 方法。仅当您确定调用方的堆栈与执行队列任务过程中所执行的任何安全检查都无关时,才能使用这些方法。QueueUserWorkItem和 RegisterWaitForSingleObject 都捕获调用方的堆栈,当线程开始执行任务时,该堆栈将合并到线程池线程的堆栈中。如果需要进行安全检查,则必须检查整个堆栈。尽管此检查提供了安全,但它还具有一定的性能开销。
使用线程池
使用线程池的方式是,从托管代码调用 ThreadPool.QueueUserWorkItem(或从非托管代码调用 CorQueueUserWorkItem)并传递表示执行任务的方法的 WaitCallback 委托。也可以通过使用 ThreadPool.RegisterWaitForSingleObject 方法并传递 WaitHandle(在向其发出信号或超时时,它将引发对 WaitOrTimerCallback 委托表示的方法的调用)来将与等待操作相关的工作项排入队列。在这两种情况下,线程池都使用一个后台线程来调用回调方法。
线程池示例
以下三个代码示例演示 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
);
}
}