您的位置:首页 > 编程语言 > C#

c#线程池详解

2011-07-23 17:15 197 查看
在这里你可以学到Microsoft研究CLR实现线程池的原理机制,从而更灵活的处理CLR在实际代码应中线程池的问题,下面我们来看看吧。
CLR教程之线程池的产生

当CLR初始化时,其线程池中不含有线程。当应用程序要创建线程来执行任务时,该应用程序应请求线程池线程来执行任务。线程池知道后将创建一个初始线程。该新线程经历的初始化和其他线程一样;但是任务完成后,该线程不会自行销毁。相反,它会以挂起状态返回线程池。如果应用程序再次向线程池发出请求,那么这个挂起的线程将激活并执行任务,而不会创建新线程。这节约了很多开销。只要线程池中应用程序任务的排队速度低于一个线程处理每项任务的速度,那么就可以反复重用同一线程,从而在应用程序生存期内节约大量开销。

那么,如果线程池中应用程序任务排队的速度超过一个线程处理任务的速度,则线程池将创建额外的线程。当然,创建新线程确实会产生额外开销,但应用程序在其生存期中很可能只请求几个线程来处理交给它的所有任务。因此,总体来说,通过使用线程池可以提高应用程序的性能。线程池的一个绝妙特性是:它是启发式的。如果您的应用程序需要执行很多任务,那么线程池将创建更多的线程。如果您的应用程序的工作负载逐渐减少,那么线程池线程将自行终止。线程池的算法确保它仅包含置于其上的工作负荷所需要的线程数!

因此,希望您现在已理解了线程池的基本概念,并明白了它所能提供的性能优势。现在我将给出一些代码来说明如何使用线程池。首先,您应该知道线程池可以提供四种功能:

◆异步调用方法

◆以一定的时间间隔调用方法

◆当单个内核对象得到信号通知时调用方法

◆当异步I/O请求结束时调用方法

前三种功能非常有用,我将在本专栏中加以说明。而应用程序开发人员很少使用第四种功能,因此在此我将不做说明;有可能在将来的专栏中讲到。

功能1:CLR线程池教程之异步调用方法

在您的应用程序中,如果有创建新线程来执行任务的代码,那么我建议您用命令线程池执行该任务的新代码来替换它。事实上,您通常会发现,让线程池执行任务比让一个新的专用线程来执行任务更容易。要排队线程池任务,您可以使用System.Threading命名空间中定义的ThreadPool类。ThreadPool类只提供静态方法,且不能构造它的实例。要让线程池线程异步调用方法,您的代码必须调用一个ThreadPool的重载QueueUserWorkItem方法,如下所示:

publicstaticBooleanQueueUserWorkItem(WaitCallbackwc,Objectstate);publicstaticBooleanQueueUserWorkItem(WaitCallbackwc);这些方法将“工作项”(和可选状态数据)排队到线程池的线程中,并立即返回。工作项只是一种方法(由wc参数标识),它被调用并传递给单个参数,即状态(状态数据)。没有状态参数的QueueUserWorkItem版本将null传递给回调方法。最后,池中的某些线程将调用您的方法来处理该工作项。您编写的回调方法必须与
System.Threading.WaitCallback委托类型相匹配,其定义如下:

publicdelegatevoidWaitCallback(Objectstate);

代码示例:

01
using
System;


02
using
System.Threading;


03

04
public
class
Test


05
{

06
//存放要计算的数值的字段


07
static
double
number1=-1;


08
static
double
number2=-1;


09

10
public
static
void
Main()


11
{

12
//获取线程池的最大线程数和维护的最小空闲线程数


13
int
maxThreadNum,portThreadNum;


14
int
minThreadNum;


15
ThreadPool.GetMaxThreads(
out
maxThreadNum,
out
portThreadNum);


16
ThreadPool.GetMinThreads(
out
minThreadNum,
out
portThreadNum);


17
Console.WriteLine(
"最大线程数:{0}"
,maxThreadNum);


18
Console.WriteLine(
"最小空闲线程数:{0}"
,minThreadNum);


19

20
//函数变量值


21
int
x=15600;


22

23
//启动第一个任务:计算x的8次方


24
Console.WriteLine(
"启动第一个任务:计算{0}的8次方。"
,x);


25
ThreadPool.QueueUserWorkItem(
new
WaitCallback(TaskProc1),x);


26

27
//启动第二个任务:计算x的8次方根


28
Console.WriteLine(
"启动第二个任务:计算{0}的8次方根。"
,x);


29
ThreadPool.QueueUserWorkItem(
new
WaitCallback(TaskProc2),x);


30

31
//等待,直到两个数值都完成计算


32
while
(number1==-1||number2==-1);


33

34
//打印计算结果


35
Console.WriteLine(
"y({0})={1}"
,x,number1+number2);


36

37
}

38

39
//启动第一个任务:计算x的8次方


40
static
void
TaskProc1(
object
o)


41
{

42
number1=Math.Pow(Convert.ToDouble(o),8);


43
}

44

45
//启动第二个任务:计算x的8次方根


46
static
void
TaskProc2(
object
o)


47
{

48
number2=Math.Pow(Convert.ToDouble(o),1.0/8.0);


49
}

50

51
}


功能2:CLR线程池教程之以一定的时间间隔调用方法

如果您的应用程序需要在某一时间执行某项任务,或者您的应用程序需要定期执行某些方法,那么使用线程池将是您的最佳选择。System.Threading命名空间定义Timer类。当您构造Timer类的实例时,您是在告诉线程池您想在将来的某个特定时间回调自己的某个方法。Timer类有四种构造函数:

publicTimer(TimerCallbackcallback,Objectstate,Int32dueTime,Int32period);publicTimer(TimerCallbackcallback,Objectstate,UInt32dueTime,UInt32period);publicTimer(TimerCallbackcallback,Objectstate,Int64dueTime,Int64period);public
Timer(TimerCallbackcallback,Objectstate,TimespandueTime,TimeSpanperiod);所有这四种构造函数构造完全相同的Timer对象。回调参数标识您想由线程池线程回调的方法。当然,您编写的回调方法必须与System.Threading.TimerCallback委托类型相匹配,其定义如下:

publicdelegatevoidTimerCallback(Objectstate);构造Timer对象后,线程池知道要做什么,并自动为您监视时间。然而,Timer类还提供了几种其他的方法,允许您与线程池进行通信,以便更改什么时候(或者是否)应当回调方法。具体地说,Timer类提供了几种Change和Dispose方法:

publicBooleanChange(Int32dueTime,Int32period);publicBooleanChange(UInt32dueTime,UInt32period);publicBooleanChange(Int64dueTime,Int64period);publicBooleanChange(TimeSpandueTime,TimeSpanperiod);publicBooleanDispose();
publicBooleanDispose(WaitHandlenotifyObject);Change方法允许您更改Timer对象的dueTime和period。Dispose方法允许您在所有挂起的回调已经完成的时候,完全取消回调,并可选地用信号通知由notifyObject参数标识的内核对象。

功能3:CLR线程池教程之当单个内核对象得到信号通知时调用方法

要让线程池线程在内核对象得到信号通知时调用您的回调方法,您可以再次利用System.Threading.ThreadPool类中定义的一些静态方法。要让线程池线程在内核对象得到信号通知时调用方法,您的代码必须调用一个重载的RegisterWaitHandle方法,当您调用这些方法之一时,h参数标识出您想要线程池等待的内核对象。由于该参数是抽象基类System.Threading.WaitHandle,因此您可以指定从该基类派生出来的任何类。特别地,您可以将一个引用传递给AutoResetEvent、ManualResetEvent
或Mutexobject。第二个参数callback标识出您想要线程池线程调用的方法。您实现的回调方法必须与System.Threading.WaitOrTimerCallback委托类型相匹配,其定义如下列代码行所示:

publicdelegatevoidWaitOrTimerCallback(Objectstate,BooleantimedOut);第三个参数state允许您指定应传递给回调方法的某些状态数据,如果没有特别的状态数据要传递,则传递null。第四个参数milliseconds允许您告诉线程池内核对象得到信号通知前应该等待的时间。这里通常传递-1,以表示无限超时。如果最后一个参数executeOnlyOnce为真,那么线程池线程将仅执行回调方法一次。但是,如果executeOnlyOnce
为假,那么线程池线程将在内核对象每次得到信号通知时执行回调方法。这对AutoResetEvent对象非常有用。

在前面所示的原型中,您会注意到RegisterWaitForSingleObject方法返回一个RegisteredWaitHandle对象。该对象确定线程池在等待的内核对象。如果由于某种原因,您的应用程序要告诉线程池停止监视已注册的等待句柄,那么您的应用程序就可以调用RegisteredWaitHandle的Unregister方法:

publicBooleanUnregister(WaitHandlewaitObject);waitObject参数表明当执行完队列中的所有工作项后,您想如何得到信号通知。如果不想得到信号通知,那么您应将null传递给该参数。如果您将一个有效引用传递给WaitHandle-derived对象,那么线程池会在已注册等待句柄的所有挂起工作项执行完后,通知该对象。

小结

在本专栏中,我讲述了对线程池的需要,说明了如何利用CLR线程池提供的各种功能。现在您应该明白线程池为您的开发所带来的价值,它可以提高您的应用程序的性能,并简化您的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: