25.5.2 使用线程池
在面向对象编程中,创建和销毁对象是很费时的,因为创建一个对象要获取内存资源或者其他更多资源。创建线程也是如此,因为Thread也是一个对象。使用一种叫做“池(Pool)”的技术可以优化对象的使用,其原理是预先创建一定数量的对象放在“池”里,在对象被请求时,某个对象被从“池”中取出供使用,使用完毕再重新放回“池”中,等待下一次的请求。使用这种机制尽可能地减少了创建和销毁对象的次数,如果是对于一些很耗资源的对象创建和销毁将更加有效。这是一种“池化资源”的技术。行文至此,可能读者朋友在想,如果有一个线程池就好了,除了选择自己来编写实现这样一个“线程池”以外,.NET Framework已经为我们提供了一个“线程池”供使用,它叫做ThreadPool,同样也位于System.Threading命名空间。
需要注意的是,线程池中的线程均为后台线程,即它们的IsBackground属性为true。这意味着在所有的前台线程都已退出后,ThreadPool中的线程不会让应用程序继续保持运行。
ThreadPool线程池多用于执行一些简单的、耗时短暂的任务,在以下情况下不应使用线程池:
❑由于线程池中的线程均为后台线程,因此当需要创建一个前台线程时不应使用线程池;
❑线程池中的线程都是默认优先级,因此当需要创建具有特定优先级的线程时不应使用线程池;
❑当需要某个任务只和特定的线程关联时,不应该使用线程池,因为无法选择执行任务时使用的是哪个具体线程;
❑当需要中止特定的线程时不应使用线程池,它不提供这个功能;
❑线程执行时间很长,线程池多用于短而多的线程任务。
要使用ThreadPool中的线程,需要使用ThreadPool.QueueUserWorkItem这个静态方法指定线程要调用的方法,该方法有2个重载版本:
❑public static bool QueueUserWorkItem(WaitCallback callBack)
❑public static bool QueueUserWorkItem(WaitCallback callBack,object state)
这两个版本都拥有一个共同的WaitCallback类型参数,WaitCallback是一个委托类型,如下所示。
public delegate void WaitCallback(object state);
可见,该委托十分类似于前文讲的ParameterizedThreadStart委托,其中state参数可以接受从外部传入的参数值,那么就不难发现前文所提到的QueueUserWorkItem方法的两个版本的区别,即第1个版本没有提供传入参数的途径;第2个版本提供了这种途径。接下来使用一段示例代码来说明线程池的使用方法,其中涉及了QueueUserWorkItem方法两种重载版本的使用。注意观察运行结果,从结果可以观察出每个正在运行的线程ID,如代码清单25-9所示。
代码清单25-9 线程池使用示例
using System;
using System.Threading;
namespace ProgrammingCSharp4
{
class AsynchronousSample
{
static void Main(string[]args)
{
ThreadPool.QueueUserWorkItem(Counter);
ThreadPool.QueueUserWorkItem(Counter,"test");
Console.WriteLine(“[线程ID={0}]主线程启动”,
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
Console.WriteLine(“主线程退出.”);
}
private static void Counter(object state)
{
Console.WriteLine(“开始计数,从1到100:”);
Console.WriteLine(“传入的参数值为:{0}”,state);
for(int counter=0;counter<100;counter++)
{
if(null==state)
{
Console.WriteLine(“[线程ID={0}]{1}”,Thread.CurrentThread.
ManagedThreadId,counter);
}
else
{
Console.WriteLine(“[线程ID={0}]{1}”,Thread.CurrentThread.
ManagedThreadId,state);
}
Thread.Sleep(100);
}
}
}
}
上述代码的运行结果如下:
[线程ID=1]主线程启动
开始计数,从1到100:
传入的参数值为:test
[线程ID=4]test
开始计数,从1到100:
传入的参数值为:
[线程ID=3]0
[线程ID=4]test
[线程ID=3]1
[线程ID=3]2
[线程ID=4]test
[线程ID=3]3
[线程ID=4]test
[线程ID=3]4
[线程ID=4]test
主线程退出.
请按任意键继续……