第10章C#多线程技术10.1线程概述在介绍线程概念之前,需要对进程与线程之间的关系做个说明。当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。线程是比进程还细的单位。一个进程可以同时存在一个以上的线程,可以将其视为进程所执行的工作。例如,在一个线程完成计算任务的同时,另外一个线程可以对图像进行更新,两个线程可以同时处理同一个进程,并发出各自不同的网络请求。从概念上讲,线程提供了一种在一个软件中并行执行代码的方式——每个线程都“同时”在一个共享的内存空间中执行指令。在具有一个处理器的计算机上,多个线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。而在Client/Server模式下,服务器要不断监听来自多个客户端的请求,这时如果采用单线程机制的话,这个线程由于不断地循环监听客户端的请求,从而无法处理其他的任务。要让服务器同时为多个客户服务,则必须引入多线程技术。多线程通常应用在以下几种情况:(1)耗时的计算工作:如遇到复杂的数学计算时,需考虑创建新的线程对象,让应用程序在进行计算的同时,使其他的线程也能够进行指定的工作;(2)等待响应信息:当用户通过网络下载大量数据时,需要较长时间的等待响应,这时可考虑应用多线程技术,让处理器去执行其他的工作。10.2.NET对多线程的支持在.NET程序设计中,在System.Threading命名空间下有一个Thread类,用于对线程进行管理。如创建线程、启动线程、终止线程、合并线程、让线程休眠等。(1)启动线程要启动某个线程,必须先创建该线程。创建线程的常用形式为:Threadt1=newThread(方法名);该线程通过委托执行指定的方法,如果方法中有参数,可在启动线程时传递实参。启动线程调用Start方法:t1.Start();//无参的方法t1.Start(“hello”);//有参方法(2)终止线程终止线程有两种方法:事先设置一个布尔类型字段,在其他线程中通过修改该布尔值表示是否需要终止该线程,在该线程中循环判断该布尔值,以确定是否退出线程。调用Thread类的Abort方法,强行终止线程。t1.Abort();(3)暂停线程在多线程应用程序中,不希望某个线程继续执行,而希望暂停一段时间,可以调用Thread类的Sleep方法。Threads.Sleep(1000);//Sleep方法的参数是暂停时间的毫秒数(4)合并线程Join方法用于把指定的线程合并到当前线程中。如果一个线程t1在执行过程中需要等待另一个线程t2结束后才能继续执行,可以在t1的代码块中调用Join方法。t2.Join();t1在执行完此语句后会处于暂停状态,直到t2结束后才会继续执行。10.3一个多线程程序在C#应用程序中,第一个线程总是Main()方法,因为第一个线程是由.NET运行库开始执行的,Main()方法是.NET运行库开始执行的第一个方法,后续的线程由应用程序在内部启动。线程的主要属性:CurrentThread:获取当前正在运行的线程。Name:设置或获取线程的名字。Priority:设置或获取线程的优先级。ThreadState:设置或获取线程的当前状态。IsBackground:指示线程是否为后台线程。IsAlive:指示当前线程的执行状态。【例10-1】打开VisualStudio.NET,新建一个控制台应用程序(ConsoleApplication),编辑以下代码:usingSystem;namespaceex1003{usingSystem;usingSystem.Threading;namespaceThreadTest{publicclassAlpha{publicvoidBeta(){while(true){Console.WriteLine(Alpha.Betaisrunninginitsownthread.);}}}publicclassSimple{publicstaticintMain(){Console.WriteLine(ThreadStart/Stop/JoinSample);AlphaoAlpha=newAlpha();ThreadoThread=newThread(newThreadStart(oAlpha.Beta));oThread.Start();while(!oThread.IsAlive);Thread.Sleep(1);oThread.Abort();oThread.Join();Console.WriteLine();Console.WriteLine(Alpha.Betahasfinished);try{Console.WriteLine(TrytorestarttheAlpha.Betathread);oThread.Start();}catch(ThreadStateException){Console.Write(ThreadStateExceptiontryingtorestartAlpha.Beta.);Console.WriteLine(Expectedsinceabortedthreadscannotberestarted.);Console.ReadLine();}return0;}}}这段程序包含两个类Alpha和Simple,在创建线程oThread时用指向Alpha.Beta()方法初始化ThreadStart代理(delegate)对象。当创建的线程oThread调用oThread.Start()方法启动时,实际上程序运行的是Alpha.Beta()方法:AlphaoAlpha=newAlpha();ThreadoThread=newThread(newThreadStart(oAlpha.Beta));oThread.Start();然后在Main()函数的while循环中,使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程oThread。然后试图用Thread.Abort()方法终止线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。可以给Thread.Join()方法指定一个int型的参数作为等待的最长时间。之后,试图用Thread.Start()方法重新启动线程oThread,但是Abort()方法带来的后果是不可恢复的终止线程,所以程序最后会抛出ThreadStateException异常。程序执行结果如图10.1所示,此例中Main()函数是C#程序的入口,起始线程可以称为主线程,如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。所有的线程虽然在微观上是串行执行的,但在宏观上完全可以认为它们在并行执行。10.4线程的优先级当线程之间争夺CPU时,CPU是按照线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是:Highest、AboveNormal、Normal、BelowNormal、Lowest。在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。给一个线程指定优先级,可以使用如下代码:myThread.Priority=ThreadPriority.Lowest;//设定优先级为最低通过设定线程的优先级,可以安排一些相对重要的线程优先执行,如对用户的响应等。以下两个例子演示设置不同线程优先级别产生的不同效果。【例10-2】两个线程优先级相同(均为默认值Normal),所以它们交替进行,每次运行,这两个线程被执行的几率大致相等。运行结果如图10.2所示。usingSystem;usingSystem.Threading;namespaceex10_1{classProgram{staticvoidMain(string[]args){ThreadThreadA=newThread(delegate(){for(inti=0;i=10000000;i++){if(i%1000000==0){Console.Write('A');}}});ThreadThreadB=newThread(delegate(){for(inti=0;i=10000000;i++){if(i%1000000==0){Console.Write('B');}}});ThreadA.Start();ThreadB.Start();Console.ReadLine();}}}【例10-3】通过设置两个线程的优先级,查看运行结果。运行结果如图10.3所示。usingSystem;usingSystem.Threading;namespaceex10_2{classProgram{staticvoidMain(string[]args){ThreadThreadA=newThread(delegate(){for(inti=0;i=20000000;i++){if(i%1000000==0){Console.Write('A');}}});ThreadThreadB=newThread(delegate(){for(inti=0;i=20000000;i++){if(i%1000000==0){Console.Write('B');}}});ThreadA.Priority=ThreadPriority.AboveNormal;ThreadB.Priority=ThreadPriority.BelowNormal;ThreadA.Start();ThreadB.Start();Console.ReadLine();}}}系统优先执行优先级较高的线程,但这只意味着优先级较高的线程占有更多的CPU时间,并不意味着一定要先执行完优先级较高的线程,才会执行优先级较低的线程。这点从运行结果中也可以看出,线程B偶尔会出现在主线程和线程A前面。若将线程A和线程B的优先级别对换,即改写下面程序段:ThreadA.Priority=ThreadPriority.BelowNormal;ThreadB.Priority=ThreadPriority.AboveNormal;10.5线程同步在应用程序中使用多线程的一个好处是,每个线程都可以异步执行。对于Windows应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理新请求。然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是Monitor类、lock关键字和Mutex类。通过对引用的对象申请一个“锁”,一旦一段程序获得该“锁”的控制权后,就可以保证只有它才能够对该对象进行操作。同样,利用这种锁,一个线程可以一直处于等待状态,直到有能够唤醒它的信号(通过变量传来)为止。1.locklock实现的功能是:使后进入的线程不会中断当前的线程,而是等待当前线程结束后再继续执行。应用:privateObjectthisLock=newobject();lock(x){//锁定的代码块,通常为使用x的语句}避免锁定public类型,否