该 System.Threading.ThreadPool 类为应用程序提供由系统管理的辅助角色线程池,使你能够专注于应用程序任务,而不是线程管理。 如果有需要后台处理的简短任务,则托管线程池是利用多个线程的一种简单方法。 在 Framework 4 及更高版本中,线程池的使用要容易得多,因为可以创建 Task 和 Task<TResult> 对象在线程池线程上执行异步任务。
.NET 出于多种目的使用线程池线程,包括 任务并行库(TPL)操作、异步 I/O 完成、计时器 回调、已注册的等待操作、使用委托的异步方法调用和 System.Net 套接字连接。
线程池特征
线程池线程是后台线程。 每个线程使用默认堆栈大小,以默认优先级运行,并且位于多线程单元中。 线程池中的线程完成其任务后,它将返回到等待线程的队列。 从此刻起,可以重复使用它。 这样,应用程序就可以避免为每个任务创建新线程的成本。
每个进程只有一个线程池。
线程池线程中的异常
线程池线程中未经处理的异常将终止进程。 此规则有三种例外情况:
- System.Threading.ThreadAbortException 在线程池线程中引发,因为调用了 Thread.Abort。
- System.AppDomainUnloadedException 在线程池线程中引发,因为正在卸载应用程序域。
- 公共语言运行时或主机进程终止线程。
有关详细信息,请参阅 托管线程中的异常。
最大线程池线程数
可以排入线程池队列的操作数量仅受可用内存的限制。 但是,线程池限制可以同时在进程中处于活动状态的线程数。 如果所有线程池线程都繁忙,其他工作项将排队,直到有线程可用来执行这些工作项。 进程的线程池的默认大小取决于几个因素,例如虚拟地址空间的大小。 进程可以调用 ThreadPool.GetMaxThreads 该方法来确定线程数。
可以使用ThreadPool.GetMaxThreads和ThreadPool.SetMaxThreads方法来控制最大线程数。
注释
承载公共语言运行时的代码可以使用该方法设置大小 ICorThreadpool::CorSetMaxThreads
。
线程池最小值
线程池按需提供新的工作线程或 I/O 完成线程,直到达到每个类别的指定最小值。 可以使用该方法 ThreadPool.GetMinThreads 获取这些最小值。
注释
当需求较低时,线程池线程的实际数量可能低于最小值。
达到最小值后,线程池可以创建其他线程,或等到某些任务完成。 线程池创建和销毁工作线程以优化吞吐量,该吞吐量定义为每个单位时间完成的任务数。 线程太少可能无法充分利用可用资源,而过多的线程可能会增加资源争用。
谨慎
可以使用该方法 ThreadPool.SetMinThreads 增加空闲线程的最小数量。 但是,不必要的增加这些值可能会导致性能问题。 如果同时启动的任务过多,则所有这些任务可能看起来都很慢。 在大多数情况下,线程池将更好地使用自己的算法分配线程。
使用线程池
使用线程池的最简单方法是使用任务并行库 (TPL)。 默认情况下,TPL 类型如 Task 和 Task<TResult> 使用线程池线程来运行任务。
还可以通过从托管代码中调用ThreadPool.QueueUserWorkItem(或从非托管代码中调用ICorThreadpool::CorQueueUserWorkItem
),并传递表示执行任务方法的System.Threading.WaitCallback委托来使用线程池。
使用线程池的另一种方法是通过使用 ThreadPool.RegisterWaitForSingleObject 方法并传递在发出信号或超时的时候调用 System.Threading.WaitHandle 委托所表示的方法的 System.Threading.WaitOrTimerCallback,从而对与等待操作相关的工作项排队。 线程池线程用于调用回调方法。
有关示例,请查看引用的 API 页。
跳过安全检查
线程池还提供 ThreadPool.UnsafeQueueUserWorkItem 和 ThreadPool.UnsafeRegisterWaitForSingleObject 方法。 仅当确定调用方的堆栈与执行排队任务期间执行的任何安全检查无关时,才使用这些方法。 ThreadPool.QueueUserWorkItem 和 ThreadPool.RegisterWaitForSingleObject 都捕获调用方堆栈,然后在线程开始执行任务时将该堆栈合并到线程池线程的堆栈中。 如果需要安全检查,则必须检查整个堆栈。 虽然检查提供了安全性,但它也有性能上的代价。
何时不使用线程池线程
有几种情况适合创建和管理自己的线程,而不是使用线程池线程:
- 需要一个前台线程。
- 需要具有特定优先级的线程。
- 拥有会导致线程长时间阻塞的任务。 由于线程池具有最大线程数,因此大量正在被阻塞的线程池线程可能导致任务无法启动。
- 需将线程放入单线程单元。 所有 ThreadPool 线程均位于多线程单元中。
- 你需要有一个与线程关联的固定标识,或者将线程专用于某个任务。