事实上系统在创建和销毁一个线程时开销是相当大的。对于线程,系统在创建时不仅要给其分配资源,而且还要在线程之间互相切换,这些都会造成程序性能 降低。为了改进这种情况,.NET提供了线程池这种技术让我们更好的利用线程。通过线程池技术,可以减少频繁的线程创建与销毁对系统性能的影响。我们可以 将线程池看作是系统自己维护的线程的集合。对于每一个进程系统都会给其创建一个线程池,你如果想要执行线程操作,你只需要向线程池发出一个执行某个操作的 请求即可。
线程池在被创建时会带有很多个工作线程,对于每个传入的请求,线程池都将分配一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请 求的处理。一旦池中的某个线程完成了任务,它将返回到线程池线程队列中等待,等待被再次征用。这种重用使应用程序可以避免为每个任务创建新线程的开销。线 程池通常具有最大线程数限制,如果所有线程都繁忙,则额外的任务将放入队列中,直到有线程可用时才能够得到处理。说到这里,有没有发现线程池有点像现实生 活中的导游公司,来一个旅游团,导游公司会为旅游团分配一个导游(相当于线程),当导游公司的导游不够时,新来的旅游团也会被放入“任务队列”中等待。
线程池中的线程默认为后台线程,即它们的IsBackground 属性为 true。这意味着在所有的前台线程都已退出后,线程池线程会自动关闭。线程池通过线程命名空间的ThreadPool类来实现,要请求由线程池中的一个 线程来处理你的任务,需要调用QueueUserWorkItem方法。此方法是一个静态方法,它使用一个委托实例作为参数,这个委托实例代表你要请求线 程池所执行的任务。要注意,当你向线程池提交一个任务请求后,你就无法再取消它了。另外,线程池中每个线程按照默认的优先级运行。
向线程池提交任务使用WaitCallback委托,我们只需要向QueueUserWorkItem方法传一个WaitCallback委托的实 例就可以向线程池添加一个任务。线程池针对这个任务会自动调用一个线程来处理。下面是WaitCallback委托的定义原型:
public delegate void WaitCallback (Object state); |
从这个委托我们可以看出两件事情,一是向线程池提交的处理方法必须是WaitCallback委托类型的,从这个委托的类型可以看出委托能够处理方 法的类型,即此方法要带一个object参数并且没有返回值。另外关于委托的参数state,其实是主线程传递给处理线程的参数,利用这个参数你可以实现 主线程和子线程简单的通讯。
下面这个例子模拟了常用的Word编辑工具的功能,我们在使用Word进行编辑时,Word本身可以在编辑的时候同时执行拼写检查、在线更新检测和 打印等功能,这些操作需要不影响主线程的编辑功能,因此它们需要使用线程或线程池来完成。使用线程池完成这个Word模拟程序的代码如下:
using System; using System.Threading; class OfficeWord { //主线程传递给处理线程的参数信息 string info = "线程池任务:"; private void Check(Object stateInfo) { //执行拼写检查操作线程执行的方法 //stateInfo是通过调用层传过来的参数对象 Console.WriteLine("{0}执行拼写检查",stateInfo); } private void Print(Object stateInfo) { //执行打印操作线程执行的方法 Console.WriteLine("{0}执行打印文档",stateInfo); } private void DownLoad(Object stateInfo) { //执行在线升级操作线程执行的方法 Console.WriteLine("{0}检查版本并进行在线升级",stateInfo); } public void Edit() { //向线程池添加处理任务,info是传给处理方法的参数信息 ThreadPool.QueueUserWorkItem(new WaitCallback(this.Check),info); ThreadPool.QueueUserWorkItem(new WaitCallback(this.Print),info); ThreadPool.QueueUserWorkItem(new WaitCallback(this.DownLoad),info); //让线程有足够时间完成任务 Thread.Sleep(1000); } static void Main(string[] args) { //测试我的Word系统 OfficeWord word = new OfficeWord(); word.Edit(); } } |
运行结果:
线程池任务:执行拼写检查
线程池任务:执行打印文档线程池任务:检查版本并进行在线升级这个例子我们向线程池提交了三个任务,线程池接受到任务后会自动安排线程处理。在这里我们使用了带有两个参数的 QueueUserWorkItem,第一个参数就是提交的任务(也就是方法),第二个参数是传递给线程处理方法的参数对象。对于 QueueUserWorkItem方法来说,还有另一种只带有一个WaitCallback参数的形式,当你不需要通过主线程传递参数给处理线程时可以 采用这种形式,如:
ThreadPool.QueueUserWorkItem(new WaitCallback(this.Print)); |
另外自己不要结束线程池中的线程,线程池创建线程并负责结束它们。当你遇到无法判断是线程池线程还是自定义线程时,你可以通过线程的IsThreadPoolThread属性来判断该线程是否是线程池线程,如果不是线程池的线程你才可以结束它:
If( !Thread.CurrentThread.IsThreadPoolThread ) Thread.CurrentThread.Abort(); |
线程池和线程一样不要随便使用,微软的MSDN为我们归纳了不用线程池的情况,从中你可看出线程和线程池的一些区别,比如线程可以是后台或前台线程,而线程池一定是后台线程等等。
在以下几种情况下,适合于创建并管理自己的线程而不是使用线程池线程:
需要前台线程。
需要使线程具有特定的优先级。
你的任务会导致线程长时间被阻止,由于线程池具有最大线程数限制,因此大量阻塞的线程池线程可能会阻止任务启动。
你需要具有与线程关联的属性时,或使某一线程专用于某一任务时。
对于需要大量线程的项目,可以考虑线程池,因为线程池能够简化你对线程管理的难度并且可以提高应用程序的性能,而且线程池也可以自动管理线程使其适 应于多个CPU的情况。线程的相关语法和使用我们就介绍到这里,可能你对线程的使用还是有点不知所措,我们将在后面介绍一些线程使用相关的内容,它们会帮 助你理解线程的使用。