托管线程池
更新:2010 年 9 月
ThreadPool 类为应用程序提供一个由系统管理的辅助线程池,从而使您可以集中精力于应用程序任务而不是线程管理。 如果您具有需要后台处理的短期任务,则托管线程池是可以利用多个线程的便捷方式。 例如,从 .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 完成、Timer 回调、注册的等待操作、使用委托的异步方法调用以及 System.Net 套接字连接。
何时不使用线程池线程
在以下几种情况下,适合于创建并管理自己的线程而不是使用线程池线程:
需要前台线程。
需要使线程具有特定的优先级。
您的任务会导致线程长时间被阻塞。 由于线程池具有最大线程数限制,因此大量阻塞的线程池线程可能会阻止任务启动。
需要将线程放入单线程单元。 所有 ThreadPool 线程均处于多线程单元中。
您需要具有与线程关联的稳定标识,或使某一线程专用于某一任务。
线程池特征
线程池线程是后台线程。 请参见前台和后台线程。 每个线程都使用默认堆栈大小,以默认的优先级运行,并处于多线程单元中。
每个进程只有一个线程池对象。
线程池线程中的异常
线程池线程中未经处理的异常将终止进程。 以下为此规则的三种例外情况:
由于调用了 Abort,线程池线程中将引发 ThreadAbortException。
由于正在卸载应用程序域,线程池线程中将引发 AppDomainUnloadedException。
公共语言运行时或宿主进程将终止线程。
有关更多信息,请参见托管线程中的异常。
注意 |
---|
在 .NET Framework 1.0 和 1.1 版中,公共语言运行时将捕获线程池线程中的未经处理的异常,而不出现任何提示。这可能会破坏应用程序状态,并最终导致应用程序挂起,将很难进行调试。 |
最大线程池线程数
可排队到线程池的操作数仅受可用内存的限制;但是,线程池限制进程中可以同时处于活动状态的线程数。 从 .NET Framework 4 版开始,进程的线程池的默认大小由虚拟地址空间的大小等多个因素决定。 进程可以调用 GetMaxThreads 方法以确定线程的数量。
通过使用 GetMaxThreads 和 SetMaxThreads 方法可以控制最大线程数。
注意 |
---|
在 .NET Framework 1.0 和 1.1 版中,不能从托管代码中设置线程池大小。承载公共语言运行时的代码可以使用 mscoree.h 中定义的 CorSetMaxThreads 设置该大小。 |
线程池最小值
线程池根据需要提供新的工作线程或 I/O 完成线程,直到其达到每个类别指定的最小值。 可使用 GetMinThreads 方法获得这些最小值。
注意 |
---|
当需求比较少时,线程池线程的实际数量可以低于这些最小值。 |
当达到最小值时,线程池可以创建更多线程或等待某些任务完成。 从 .NET Framework 4 开始,线程池会创建和销毁工作线程以优化吞吐量,吞吐量定义为单位时间内完成的任务数。 线程过少时可能无法更好地利用可用资源,但线程过多时又可能会加剧资源的争用情况。
警告 |
---|
您可以使用 SetMinThreads 方法增加空闲线程的最小数量。但是,在不必要的情况下增加这些值,可能会导致性能问题。如果同时启动的任务过多,则所有任务的处理速度看起来都可能很慢。大多数情况下,线程池使用自己的分配线程的算法将能够更好地工作。 |
跳过安全检查
线程池还提供了 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject 方法。 仅在确定调用方的堆栈与执行排队任务的过程中执行的任何安全检查无关时使用这些方法。 QueueUserWorkItem 和 RegisterWaitForSingleObject 都捕获调用方的堆栈,在线程开始执行任务时将此堆栈合并到线程池线程的堆栈中。 如果需要进行安全检查,则必须检查整个堆栈。 尽管此检查提供了安全,但它还具有一定的性能开销。
使用线程池
从 .NET Framework 4 开始,使用线程池最简单的方式是使用任务并行库。 默认情况下,Task 和 Task<TResult> 等并行库类型使用线程池线程运行任务。 还可通过如下方式使用线程池:从托管代码调用 ThreadPool.QueueUserWorkItem(或从非托管代码调用 CorQueueUserWorkItem)并传递表示执行任务的方法的 WaitCallback 委托。 使用线程池的另一种方式是:通过使用 ThreadPool.RegisterWaitForSingleObject 方法并传递 WaitHandle(在向其发出信号或超时时,它将调用 WaitOrTimerCallback 委托表示的方法)来对与等待操作相关的工作项排队。 线程池线程用于调用回调方法。
线程池示例
本节中的代码示例通过使用 Task 类、ThreadPool.QueueUserWorkItem 方法和 ThreadPool.RegisterWaitForSingleObject 方法来演示线程池。
使用任务并行库执行异步任务
使用 QueueUserWorkItem 异步执行代码
为 QueueUserWorkItem 提供任务数据
使用 RegisterWaitForSingleObject
使用任务并行库执行异步任务
下面的示例演示如何通过调用 TaskFactory.StartNew 方法来创建和使用 Task 对象。 有关使用 Task<TResult> 类从异步任务返回值的示例,请参见如何:从任务中返回值。
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 修复 |