ThreadPool 线程池

线程池是一个好东西。特别是在BS模式下的应用,对于多线程操作的有序和稳定实际展现的效果都还算让人满意的。

ThreadPool 类 提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。
许 多应用程序创建的线程都要在休眠状态中消耗大量时间,以等待事件发生。其他线程可能进入休眠状态,只被定期唤醒以轮询更改或更新状态信息。线程池通过为应 用程序提供一个由系统管理的辅助线程池使您可以更为有效地使用线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一 个辅助线程就会执行对应的回调函数。

注意
托管线程池中的线程为后台线程,即它们的 IsBackground 属性为 true。这意味着在所有的前台线程都已退出后,ThreadPool 线程不会让应用程序保持运行。


也可以将与等待操作不相关的工作项排列到线程池。若要请求由线程池中的一个线程来处理工作项,请调用 QueueUserWorkItem 方法。此方法将对将被从线程池中选定的线程调用的方法或委托的引用用作参数。一个工作项排入队列后就无法再取消它。

计时器队列中的计时器以及已注册的等待操作也使用线程池。它们的回调函数也会排列到线程池。

每个进程都有一个线程池。线程池的默认大小为每个可用处理器有 25 个线程。使用 SetMaxThreads 方法可以更改线程池中的线程数。每个线程使用默认的堆栈大小并按照默认的优先级运行。

WaitCallback 委托 表示线程池线程要执行的回调方法。
WaitCallback 表示要在 ThreadPool 线程上执行的回调方法。创建委托,方法是将回调方法传递给 WaitCallback 构造函数。您的方法必须具有此处所显示的签名。

通过将 WaitCallback 委托传递给 ThreadPool.QueueUserWorkItem 来将任务排入队列以便执行。您的回调方法将在某个线程池线程可用时执行。

注意
Visual Basic 用户可以省略 WaitCallback 构造函数,在向 QueueUserWorkItem 传递回调方法时只需使用 AddressOf 运算符即可。Visual Basic 将自动调用正确的委托构造函数。


如果要将信息传递给回调方法,请创建包含所需信息的对象,并在将任务排入队列以便执行时将它传递给 QueueUserWorkItem。每次执行您的回调方法时,state 参数都包含此对象。

ThreadPool的目标大抵都是为了减除线程的初始化开销,更好的实现并行处理。.NET类库中的ThreadPool是异步IO的基础,比如,在System.Net.Socket中,我们可以使用BeginAccept , EndAccept将Socket需要阻塞的操作放到系统的线程池中运行,而在执行结束以后通知主线程。

即使你没有在线程中显示的调用过ThreadPool的方法,只要你在写.NET程序,你就可能已经在使用线程池了,不信,打开一个.NET程序,在任务管理器中看看他的线程数,你会发现有N个线程运行中,即使你可能只使用了一个线程。如果你用了异步API的话,线程数目可能会让你觉得目瞪口呆。

ThreadPool的是一个静态类,它没有定义任何的构造方法,我们只能够使用它的静态方法,这是因为,这是因为ThreadPool是托管线程池,是由CLR管理的。

在我们的程序中也可以使用ThreadPool来进行一些比较耗时或者需要阻塞的操作。接下来我们将通过几个比较基本的例子开始我们的ThreadPool应用。

首先是我们如何将一个耗时的方法加入ThreadPool执行,这涉及到

ThreadPool.QueueUserWorkItem这个方法。

它的原型如下:

public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack,object state);

public delegate void WaitCallback(object state);

下面是一个简单,但有效的WaitCallback定义,他展示了一种使用的方法:

static void testStatic(Object obj)       

{            //获取线程外部参数,并转换成需要的类型

testObject tobj = obj as testObject;            

tobj.calc();

}   

对于其传递的参数 obj,主要是作为内部调用使用的,可以将它理解成LPARAM类似的结构,向线程传递更多的执行信息。

主线程中使用下面的语句将该操作加入线程池:

ThreadPool.QueueUserWorkItem(testStatic, new testObject());

后面一个对象是传递给委托实例(testStatic)的参数   

主线程如果方法定义于不同的类中或者是实例方法的话,需要加上类型或者实例名称,

如:TestObj.testStatictheObj.testStatic之类的

通常是将计算密集型的操作放在worker线程池中运行,而线程池的大小会根据当前的CPU使用量自动调整,通过下面两个方法,我们可以设置线程池的大小:

ThreadPool.SetMaxThreads(10, 200);ThreadPool.SetMinThreads(2, 40);

两个参数分别是WorkThreadIO thread的限制。 接下来我们来考虑如何运行一些重复的工作,如何使用ThreadPool来调度一些需要周期性运行的工作,.NET提供了System.Threading.Timer类实现这一个功能。使用涉及TimerTimerCallback。后者也是一个委托,其声明如下:

public delegate void TimerCallback(object state);

显然,他的使用方法与上面WaitCallback的完全相同,我们可以简单的将上面的例子变成周期性运行的:

Timer tm = new Timer(new TimerCallback(testStatic),new testObject(),0,2000);

后面的两个参数是启动的延迟时间和周期

Timer的线程分配机制与当前同时进行的其它Timer的时间复杂度有关系,当定义几个Timer同时工作的时候,如果每一个操作耗时较长,而且可能同时到期的话,线程池可能为每一个Timer操作定义不同的执行线程,而对于简单操作,有可能多个Timer被放在同一个线程中执行。

ThreadPool的另外一个很有用的场合是基于同步对象的调用,很多的场合下,我们会使用同步对象来调度多线程的运行,当某一个同步对象被通知的时候,等候的线程继续执行,而在此之前,这些线程将会阻塞。

常用的同步对象有以下几个:

Mutex , AutoResetEvent和ManualResetEvent。具体使用方法也比较简单,只是对于不同的同步对象,获取和释放的方法不同。

Mutex mtx = new Mutex(true);           

ThreadPool.RegisterWaitForSingleObject(mtx,testMutex, mtx, 4000, true);            mtx.ReleaseMutex();

是一个使用Mutex的例子,构造函数为true,则当前线程拥有Mutex。其它线程使用WaitOne函数等待该mutex。

posted @ 2009-07-17 17:46  起源  阅读(729)  评论(0)    收藏  举报