许多应用程序使用多个线程,但这些线程经常在休眠状态中耗费大量的时间来等待事件发生。其他线程可能进入休眠状态,并且仅定期被唤醒以轮询更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NET框架为每一个进程提供了一个线程池,使应用程序能够根据需要来有效地利用多个线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的回调函数。线程池中的线程由系统进行管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间之后创建另一个辅助线程。但线程的数目永远不会超过最大值。超过最大值的其他线程可以排队,但它们要等到其他线程完成后才启动。
线程池特别适合于执行一些需要多个线程的任务。使用线程池能够优化这些任务的执行过程,从而提高吞吐量,它不仅能够使系统针对此进程优化该执行过程,而且还能够使系统针对计算机上的其他进程优化该执行过程。如果需要启动多个不同的任务,而不想分别设置每个线程的属性,则可以使用线程池。
如果应用程序需要对线程进行特定的控制,则不适合使用线程池,需要创建并管理自己的线程。不适合使用线程池的情形包括:
— 如果需要使一个任务具有特定的优先级。
— 如果具有可能会长时间运行(并因此阻塞其他任务)的任务。
— 如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)。
— 如果需要用永久标识来标识和控制线程,比如想使用专用线程来中止该线程,将其挂起或按名称发现它。
System.Threading.ThreadPool类实现了线程池。ThreadPool类是一个静态类,它提供了管理线程池的一系列方法。
ThreadPool.QueueUserWorkItem方法在线程池中创建一个线程池线程来执行指定的方法(用委托WaitCallback来表示),并将该线程排入线程池的队列等待执行。QueueUserWorkItem方法的原型为:
public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
public static Boolean QueueUserWorkItem(WaitCallback wc);
这些方法将“工作项”(和可选状态数据)排列到线程池的线程中,并立即返回。工作项只是一种方法(由wc参数标识),它被调用并传递给单个参数,即状态(状态数据)。没有状态参数的QueueUserWorkItem版本将null传递给回调方法。线程池中的某些线程将调用System.Threading.WaitCallback委托表示的回调方法来处理该工作项。回调方法必须与System.Threading.WaitCallback委托类型相匹配。WaitCallback定义如下:
public delegate void WaitCallback(Object state);
调用QueueUserWorkItem时传入的Object类型参数将传递到任务过程,可以通过这种方式来向任务过程传递参数。如果任务过程需要多个参数,可以定义包含这些数据的类,并将类的实例强制转换为Object数据类型。
每个进程都有且只有一个线程池。当进程启动时,线程池并不会自动创建。当第一次将回调方法排入队列(比如调用ThreadPool.QueueUserWorkItem方法)时才会创建线程池。一个线程监视所有已排队到线程池中的任务。当某项任务完成后,线程池中的线程将执行相应的回调方法。在对一个工作项进行排队之后将无法取消它。
线程池中的线程数目仅受可用内存的限制。但是,线程池将对允许在进程中同时处于活动状态的线程数目强制实施限制(这取决于CPU的数目和其他因素)。默认情况下,每个系统处理器最多可以运行25个线程池线程。通过使用ThreadPool.GetMaxThreads和ThreadPool.SetMax Threads方法,可以获取和设置线程池的最大线程数。
即使是在所有线程都处于空闲状态时,线程池也会维持最小的可用线程数,以便队列任务可以立即启动。将终止超过此最小数目的空闲线程,以节省系统资源。默认情况下,每个处理器维持一个空闲线程。使用ThreadPool.GetMinThreads和ThreadPool.SetMinThreads方法可以获取和设置线程池所维持的空闲线程数。
下面的程序用于计算如下的函数的值:
// ThreadPool.cs
// 线程池示例
using System;
using System.Threading;
public class Test
{
// 存放要计算的数值的字段
static double number1 = -1;
static double number2 = -1;
public static void Main()
{
// 获取线程池的最大线程数和维护的最小空闲线程数
int maxThreadNum, portThreadNum;
int minThreadNum;
ThreadPool.GetMaxThreads(out maxThreadNum, out portThreadNum);
ThreadPool.GetMinThreads(out minThreadNum, out portThreadNum);
Console.WriteLine("最大线程数:{0}", maxThreadNum);
Console.WriteLine("最小空闲线程数:{0}", minThreadNum);
// 函数变量值
int x = 15600;
// 启动第一个任务:计算x的8次方
Console.WriteLine("启动第一个任务:计算{0}的8次方。", x);
ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), x);
// 启动第二个任务:计算x的8次方根
Console.WriteLine("启动第二个任务:计算{0}的8次方根。", x);
ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), x);
// 等待,直到两个数值都完成计算
while (number1 == -1 || number2 == -1) ;
// 打印计算结果
Console.WriteLine("y({0}) = {1}", x, number1 + number2);
}
// 启动第一个任务:计算x的8次方
static void TaskProc1(object o)
{
number1 = Math.Pow(Convert.ToDouble(o), 8);
}
// 启动第二个任务:计算x的8次方根
static void TaskProc2(object o)
{
number2 = Math.Pow(Convert.ToDouble(o), 1.0 / 8.0);
}
}
该程序的输出结果为:
最大线程数:25
最小空闲线程数:1
启动第一个任务:计算15600的8次方。
启动第二个任务:计算15600的8次方根。
y(15600) = 3.50749278894883E+33