浅谈C#多线程简单例子讲解
.NET将关于多线程的功能定义在System.Threading名字空间中。因此,要使用多线程,必须先声明引用此名字空间(usingSystem.Threading;)。
a.启动线程
顾名思义,“启动线程”就是新建并启动一个线程的意思,如下代码可实现:
Threadthread1=newThread(newThreadStart(Count));
其中的Count是将要被新线程执行的函数。
b.杀死线程
“杀死线程”就是将一线程斩草除根,为了不白费力气,在杀死一个线程前最好先判断它是否还活着(通过IsAlive属性),然后就可以调用Abort方法来杀死此线程。
c.暂停线程
它的意思就是让一个正在运行的线程休眠一段时间。如thread.Sleep(1000);就是让线程休眠1秒钟。
d.优先级
这个用不着解释了。Thread类中hreadPRiority属性,它用来设置优先级,但不能保证操作系统会接受该优先级。一个线程的优先级可分为5种:Normal,AboveNormal,BelowNormal,Highest,Lowest。具体实现例子如下:
thread.Priority=ThreadPriority.Highest;
e.挂起线程
Thread类的Suspend方法用来挂起线程,直到调用Resume,此线程才可以继续执行。如果线程已经挂起,那就不会起作用。
if(thread.ThreadState=ThreadState.Running) { thread.Suspend(); }
f.恢复线程
用来恢复已经挂起的线程,以让它继续执行,如果线程没挂起,也不会起作用。
if(thread.ThreadState=ThreadState.Suspended) { thread.Resume(); }
下面将列出一个例子,以说明简单的线程处理功能。此例子来自于帮助文档。
usingSystem; usingSystem.Threading; //Simplethreadingscenario:Startastaticmethodrunning //onasecondthread. publicclassThreadExample{ //TheThreadProcmethodiscalledwhenthethreadstarts. //Itloopstentimes,writingtotheconsoleandyielding //therestofitstimesliceeachtime,andthenends. publicstaticvoidThreadProc(){ for(inti=0;i<10;i++){ Console.WriteLine("ThreadProc:{0}",i); //Yieldtherestofthetimeslice. Thread.Sleep(0); } } publicstaticvoidMain(){ Console.WriteLine("Mainthread:Startasecondthread."); //TheconstructorfortheThreadclassrequiresaThreadStart //delegatethatrepresentsthemethodtobeexecutedonthe //thread.C#simplifiesthecreationofthisdelegate. Threadt=newThread(newThreadStart(ThreadProc)); //StartThreadProc.Onauniprocessor,thethreaddoesnotget //anyprocessortimeuntilthemainthreadyields.Uncomment //theThread.Sleepthatfollowst.Start()toseethedifference. t.Start(); //Thread.Sleep(0); for(inti=0;i<4;i++){ Console.WriteLine("Mainthread:Dosomework."); Thread.Sleep(0); } Console.WriteLine("Mainthread:CallJoin(),towaituntilThreadProcends."); t.Join(); Console.WriteLine("Mainthread:ThreadProc.Joinhasreturned.PressEntertoendprogram."); Console.ReadLine(); } }
此代码产生的输出类似如下内容:
Mainthread:Startasecondthread. Mainthread:Dosomework. ThreadProc:0 Mainthread:Dosomework. ThreadProc:1 Mainthread:Dosomework. ThreadProc:2 Mainthread:Dosomework. ThreadProc:3 Mainthread:CallJoin(),towaituntilThreadProcends. ThreadProc:4 ThreadProc:5 ThreadProc:6 ThreadProc:7 ThreadProc:8 ThreadProc:9 Mainthread:ThreadProc.Joinhasreturned.PressEntertoendprogram.
在VisulC#中System.Threading命名空间提供一些使得可以进行多线程编程的类和接口,其中线程的创建有以下三种方法:Thread、ThreadPool、Timer。下面就它们的使用方法逐个作一简单介绍。
一、Thread
这也许是最复杂的方法,但它提供了对线程的各种灵活控制。首先你必须使用它的构造函数创建一个线程实例,它的参数比较简单,只有一个ThreadStart委托:publicThread(ThreadStartstart);然后调用Start()启动它,当然你可以利用它的Priority属性来设置或获得它的运行优先级(enumThreadPriority:Normal、Lowest、Highest、BelowNormal、AboveNormal)。
下例首先生成了两个线程实例t1和t2,然后分别设置它们的优先级,接着启动两线程(两线程基本一样,只不过它们输出不一样,t1为“1”,t2为“2”,根据它们各自输出字符个数比可大致看出它们占用CPU时间之比,这也反映出了它们各自的优先级)。
staticvoidMain(string[]args) { Threadt1=newThread(newThreadStart(Thread1)); Threadt2=newThread(newThreadStart(Thread2)); t1.Priority=ThreadPriority.BelowNormal; t2.Priority=ThreadPriority.Lowest; t1.Start(); t2.Start(); } publicstaticvoidThread1() { for(inti=1;i<1000;i++) {//每运行一个循环就写一个“1” dosth(); Console.Write("1"); } } publicstaticvoidThread2() { for(inti=0;i<1000;i++) {//每运行一个循环就写一个“2” dosth(); Console.Write("2"); } } publicstaticvoiddosth() {//用来模拟复杂运算 for(intj=0;j<10000000;j++) { inta=15; a=a*a*a*a; } }
以上程序运行结果为:
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
11111111111111111111111111111111111111111121111111111111111111111111111111111111111112
从以上结果我们可以看出,t1线程所占用CPU的时间远比t2的多,这是因为t1的优先级比t2的高,若我们把t1和t2的优先级都设为Normal,结果见下:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
从上例我们可看出,它的构造类似于win32的工作线程,但更加简单,只需把线程要调用的函数作为委托,然后把委托作为参数构造线程实例即可。当调用Start()启动后,便会调用相应的函数,从那函数第一行开始执行。
接下来我们结合线程的ThreadState属性来了解线程的控制。ThreadState是一个枚举类型,它反映的是线程所处的状态。当一个Thread实例刚创建时,它的ThreadState是Unstarted;当此线程被调用Start()启动之后,它的ThreadState是Running;在此线程启动之后,如果想让它暂停(阻塞),可以调用Thread.Sleep()方法,它有两个重载方法(Sleep(int)、Sleep(Timespan)),只不过是表示时间量的格式不同而已,当在某线程内调用此函数时,它表示此线程将阻塞一段时间(时间是由传递给Sleep的毫秒数或Timespan决定的,但若参数为0则表示挂起此线程以使其它线程能够执行,指定Infinite以无限期阻塞线程),此时它的ThreadState将变为WaitSleepJoin,另外值得注意一点的是Sleep()函数被定义为了static?!这也意味着它不能和某个线程实例结合起来用,也即不存在类似于t1.Sleep(10)的调用!正是如此,Sleep()函数只能由需“Sleep”的线程自己调用,不允许其它线程调用,正如whentoSleep是个人私事不能由它人决定。但是当某线程处于WaitSleepJoin状态而又不得不唤醒它时,可使用Thread.Interrupt方法,它将在线程上引发ThreadInterruptedException,下面我们先看一个例子(注意Sleep的调用方法):
staticvoidMain(string[]args) { Threadt1=newThread(newThreadStart(Thread1)); t1.Start(); t1.Interrupt(); E.WaitOne(); t1.Interrupt(); t1.Join(); Console.WriteLine(“t1isend”); } staticAutoResetEventE=newAutoResetEvent(false); publicstaticvoidThread1() { try {//从参数可看出将导致休眠 Thread.Sleep(Timeout.Infinite); } catch(System.Threading.ThreadInterruptedExceptione) {//中断处理程序 Console.WriteLine("1stinterrupt"); } E.Set(); try {//休眠 Thread.Sleep(Timeout.Infinite); } catch(System.Threading.ThreadInterruptedExceptione) { Console.WriteLine("2ndinterrupt"); }//暂停10秒 Thread.Sleep(10000); }
运行结果为:
1stinterrupt
2ndinterrupt
(10s后)t1isend
从上例我们可以看出Thread.Interrupt方法可以把程序从某个阻塞(WaitSleepJoin)状态唤醒进入对应的中断处理程序,然后继续往下执行(它的ThreadState也变为Running),此函数的使用必须注意以下几点:
1、此方法不仅可唤醒由Sleep导致的阻塞,而且对一切可导致线程进入WaitSleepJoin状态的方法(如Wait和Join)都有效。如上例所示,使用时要把导致线程阻塞的方法放入try块内,并把相应的中断处理程序放入catch块内。
2、对某一线程调用Interrupt,如它正处于WaitSleepJoin状态,则进入相应的中断处理程序执行,若此时它不处于WaitSleepJoin状态,则它后来进入此状态时,将被立即中断。若在中断前调用几次Interrupt,只有第一次调用有效,这正是上例我用同步的原因,这样才能确保第二次调用Interrupt在第一个中断后调用,否则的话可能导致第二次调用无效(若它在第一个中断前调用)。你可以把同步去掉试试,其结果很可能是: 1stinterrupt
上例还用了另外两个使线程进入WaitSleepJoin状态的方法:利用同步对象和Thread.Join方法。Join方法的使用比较简单,它表示在调用此方法的当前线程阻塞直至另一线程(此例中是t1)终止或者经过了指定的时间为止(若它还带了时间量参数),当两个条件(若有)任一出现,它立即结束WaitSleepJoin状态进入Running状态(可根据.Join方法的返回值判断为何种条件,为true,则是线程终止;false则是时间到)。线程的暂停还可用Thread.Suspend方法,当某线程处于Running状态时对它调用Suspend方法,它将进入SuspendRequested状态,但它并不会被立即挂起,直到线程到达安全点之后它才可以将该线程挂起,此时它将进入Suspended状态。如对一个已处于Suspended的线程调用则无效,要恢复运行只需调用Thread.Resume即可。
最后我们谈的是线程的销毁,我们可以对需销毁的线程调用Abort方法,它会在此线程上引发ThreadAbortException。我们可把线程内的一些代码放入try块内,并把相应处理代码放入相应的catch块内,当线程正执行try块内代码时如被调用Abort,它便会跳入相应的catch块内执行,执行完catch快内的代码后它将终止(若catch块内执行了ResetAbort则不同了:它将取消当前Abort请求,继续向下执行。所以如要确保某线程终止的最好用Join,如上例)。
二、ThreadPool
线程池(ThreadPool)是一种相对较简单的方法,它适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程),它的缺点是对创建的线程不能加以控制,也不能设置其优先级。由于每个进程只有一个线程池,当然每个应用程序域也只有一个线程池(对线),所以你将发现ThreadPool类的成员函数都为static!当你首次调用ThreadPool.QueueUserWorkItem、ThreadPool.RegisterWaitForSingleObject等,便会创建线程池实例。下面就线程池当中的两函数作一介绍:
publicstaticboolQueueUserWorkItem(//调用成功则返回true WaitCallbackcallBack,//要创建的线程调用的委托 objectstate//传递给委托的参数 )//它的另一个重载函数类似,只是委托不带参数而已
此函数的作用是把要创建的线程排队到线程池,当线程池的可用线程数不为零时(线程池有创建线程数的限制,缺身值为25),便创建此线程,否则就排队到线程池等到它有可用的线程时才创建。
publicstaticRegisteredWaitHandleRegisterWaitForSingleObject( WaitHandlewaitObject,//要注册的WaitHandle WaitOrTimerCallbackcallBack,//线程调用的委托 objectstate,//传递给委托的参数 intTimeOut,//超时,单位为毫秒, boolexecuteOnlyOncefile://是否只执行一次 ); publicdelegatevoidWaitOrTimerCallback( objectstate,//也即传递给委托的参数 booltimedOut//true表示由于超时调用,反之则因为waitObject );
此函数的作用是创建一个等待线程,一旦调用此函数便创建此线程,在参数waitObject变为终止状态或所设定的时间TimeOut到了之前,它都处于“阻塞”状态,值得注意的一点是此“阻塞”与Thread的WaitSleepJoin状态有很大的不同:当某Thread处于WaitSleepJoin状态时CPU会定期的唤醒它以轮询更新状态信息,然后再次进入WaitSleepJoin状态,线程的切换可是很费资源的;而用此函数创建的线程则不同,在触发它运行之前,CPU不会切换到此线程,它既不占用CPU的时间又不浪费线程切换时间,但CPU又如何知道何时运行它?实际上线程池会生成一些辅助线程用来监视这些触发条件,一旦达到条件便启动相应的线程,当然这些辅助线程本身也占用时间,但是如果你需创建较多的等待线程时,使用线程池的优势就越加明显。见下例:
staticAutoResetEventev=newAutoResetEvent(false); publicstaticintMain(string[]args) {ThreadPool.RegisterWaitForSingleObject( ev, newWaitOrTimerCallback(WaitThreadFunc), 4, 2000, false//表示每次完成等待操作后都重置计时器,直到注销等待 ); ThreadPool.QueueUserWorkItem(newWaitCallback(ThreadFunc),8); Thread.Sleep(10000); return0; } publicstaticvoidThreadFunc(objectb) {Console.WriteLine("theobjectis{0}",b); for(inti=0;i<2;i++) {Thread.Sleep(1000); ev.Set(); } } publicstaticvoidWaitThreadFunc(objectb,boolt) {Console.WriteLine("theobjectis{0},tis{1}",b,t); }
其运行结果为:
theobjectis8
theobjectis4,tisFalse
theobjectis4,tisFalse
theobjectis4,tisTrue
theobjectis4,tisTrue
theobjectis4,tisTrue
从以上结果我们可以看出线程ThreadFunc运行了1次,而WaitThreadFunc运行了5次。我们可以从WaitOrTimerCallback中的boolt参数判断启动此线程的原因:t为false,则表示由于waitObject,否则则是由于超时。另外我们也可以通过objectb向线程传递一些参数。
3、Timer
它适用于需周期性调用的方法,它不在创建计时器的线程中运行,它在由系统自动分配的单独线程中运行。这和Win32中的SetTimer方法类似。它的构造为:
publicTimer( TimerCallbackcallback,//所需调用的方法 objectstate,//传递给callback的参数 intdueTime,//多久后开始调用callback intperiod//调用此方法的时间间隔 );
//如果dueTime为0,则callback立即执行它的首次调用。如果dueTime为Infinite,则callback不调用它的方法。计时器被禁用,但使用Change方法可以重新启用它。如果period为0或Infinite,并且dueTime不为Infinite,则callback调用它的方法一次。计时器的定期行为被禁用,但使用Change方法可以重新启用它。如果period为零(0)或Infinite,并且dueTime不为Infinite,则callback调用它的方法一次。计时器的定期行为被禁用,但使用Change方法可以重新启用它。
在创建计时器之后若想改变它的period和dueTime,我们可以通过调用Timer的Change方法来改变:
publicboolChange( intdueTime, intperiod );//显然所改变的两个参数对应于Timer中的两参数 publicstaticintMain(string[]args) { Console.WriteLine("periodis1000"); Timertm=newTimer(newTimerCallback(TimerCall),3,1000,1000); Thread.Sleep(2000); Console.WriteLine("periodis500"); tm.Change(0,800); Thread.Sleep(3000); return0; } publicstaticvoidTimerCall(objectb) { Console.WriteLine("timercallback;bis{0}",b); }
其运行结果为:
periodis1000
timercallback;bis3
timercallback;bis3
periodis500
timercallback;bis3
timercallback;bis3
timercallback;bis3
timercallback;bis3
总结
从以上的简单介绍,我们可以看出它们各自使用的场合:Thread适用于那些需对线程进行复杂控制的场合;ThreadPool适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程);Timer则适用于那些需周期性调用的方法。只要我们了解了它们的使用特点,我们就可以很好的选择合适的方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。