版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
原文链接/leslies2/archive/2012/02/07/2310495.htm引言本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发。其中委托的Beginlnvoke方法以及回调函数最为常用。而I/〇线程可能容易遭到大家的忽略,其实在开发多线程系统,更应该多留意I/〇线程的操作。特别是在ASP.NET开发当中,可能更多人只会留意在客户端使用Ajax或者在服务器端使用UpdatePaneL其实合理使用I/O线程在通讯项目或文件下载时,能尽可能地减少iis的压カ。并行编程是Framework4.0中极カ推广的异步操作方式,更值得更深入地学习。希望本篇文章能对各位的学习研究有所帮助,当中有所错漏的地方敬请点评。目录ー、线程的定义二、线程的基础知识三、以Threadstart方式实现多线程四、CLR线程池的工作者线程五、CLR线程池的I/O线程六、异步SqlCommand七、并行编程与PLINOハ、计时器与锁一、线程的定义1进程、应用程序域与线程的关系进程(Process)是Windows系统中的一个基本概念,它包含着,ー个运行程序所需要的资源。进程之间是相对独立的,ー个进程无法访问另ー个进程的数据(除非利用分布式计算方式),ー个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为ー个程序的基本边界。应用程序域(AppDomain)是ー个程序运行的逻辑区域,它可以视为ー个轻量级的进程,.NET的程序集正是在应用程序域中运行的,一个进程可以包含有多个应用程序域,•・个应用程序域也可以包含多个程序集。在ー个应用程序域中包含了一个或多个上下文context,使用上下文CLR就能够把某些特殊对象的状态放置在不同容器当中。线程(Thread)是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main。方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(ThreadLocalStorage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。进程、应用程序域、线程的关系如下图,ー个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于ー个应用程序域内。由于本文是以介绍多线程技术为主题,对进程、应用程序域的介绍就到此为止。关于进程、线程、应用程序域的技术,在“C#综合揭秘——细说进程、应用程序域与上下文”会有详细介绍。1.2多线程在单CPU系统的一个单位时间(timeslice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保存到线程的本地存储器(TLS)中,以便下次执行时恢复执行。而多线程只是系统带来的ー个假像,它在多个单位时间内进行多个线程的切换。因为切换频密而且单位时间非常短暂,所以多线程可被视作同时运行。适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。返回目录
二、线程的基础知识System.Threading.Thread类System.Threading.Thread是用于控制线程的基础类,通过Thread可以捽制当前应用程序域中线程的创建、挂起、停止、销毁。它包括以下常用公共属性:属性名称说明Currentcontext获取线程正在其中执行的当前上下文。CurrentThread获取当前正在运行的线程。Executioncontext获取ー个Executioncontext对象,该对象包含有关当前线程的各种上ド文的信息。IsAlive获取・个值,该值指示当前线程的执行状态。IsBackground获取或设置ー个值,该值指示某个线程是否为后台线程。IsThreadPoolThread获取ー个值,该值指示线程是否属于托管线程池。ManagedThreadld获取当前托管线程的唯•标识符。Name获取或设置线程的名称。Priority获取或设置・个值,该值指示线程的调度优先级。ThreadState获取ー个值,该值包含当前线程的状态。线程的标识符ManagedThreadld是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadld来辨别线程的。而Name是ー个可变值,在默认时候,Name为ー个空值Null,开发人员可以通过程序设置线程的名称,但这只是ー个辅助功能。线程的优先级别,NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。成员名称Lowest说明可以将Thread安排在具有任何其他优先级的线程之后。BelowNormal可以将Thread安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前。Normal默认选择。可以将Thread安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。AboveNormal可以将Thread安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前。Highest可以将Thread安排在具有任何其他优先级的线程之前。
线程的状态通过Threadstate可以检测线程是处于Unstarted、Sleeping、Running等等状态,它比IsAlive属性能提供更多的特定信息。前面说过,ー个应用程序域中可能包括多个上下文,而通过Currentcontext可以获取线程当前的上下文。CurrentThread是最常用的ー个属性,它是用于获取当前运行的线程。2.1.4System.Threading.Thread的方法Thread中包括了多个方法来控制线程的创建、挂起、停止、销毁,以后来的例子中会经常使用。方法名称说明Abort()终止本线程。GetDomain()返冋当前线程正在其中运行的当前域。GetDomainld()返回当前线程正在其中运行的当前域Idolnterrupt()中断处于WaitSleepJoin线程状态的线程。Join()已重载。阻塞调用线程,直到某个线程终止时为止。Resume()继续运行已挂起的线程。Start()执行本线程。Suspend()挂起当前线程,如果当前线程已属于挂起状态则此不起作用Sleep()把正在运行的线程挂起•段时间。.5开发实例以下这个例子,就是通过Thread显示当前线程信息电staticvoidMain(string[]args)(Threadthread=Thread.CurrentThread;4 thread.Name="MainThread";stringthreadMessage=string.Format("ThreadID:{0}\nCurrentAppDomainld:{1}\n"+6 "CurrentContextld:{2}\nThreadName:{3}\n"+"ThreadState:{4}\nThreadPriority:{5}\n",thread.ManagedThreadld,Thread.GetDornainID(),Thread.Currenteontext.ContextID,thread.Name,thread.Threadstate,thread.Priority);Console.WriteLine(threadMessage);Console.ReadKey();运行结果System.Threading命名空间在System.Threading命名空间内提供多个方法来构建多线程应用程序,其中ThreadPool与Thread是多线程开发中最常用到的,在.NET中专门设定了一个CLR线程池专门用于管理线程的运行,这个CLR线程池正是通过ThreadPool类来管理。而Thread是管理线程的最直接方式,下面几节将详细介绍有关内容。类说明AutoResetEvent通知正在等待的线程已发生事件。无法继承此类。Executioncontext管理当前线程的执行上下文。无法继承此类。Interlocked为多个线程共享的变量提供原子操作。Monitor提供同步对对象的访问的机制。Mutexー个同步基元,也可用于进程间同步。Thread创建并控制线程,设置其优先级并获取其状态。ThreadAbortException在对Abort方法进行调用时引发的异常。无法继承此类。ThreadPool提供ー个线程池,该线程池可用于发送工作项、处理异步I/O,代表其他线程等待以及处理计时器。Timeout包含用丁•指定无限长的时间的常数。无法继承此类。Timer提供以指定的时间间隔执行方法的机制。无法继承此类。WaitHandle封装等待对共享资源的独占访问的操作系统特定的对象。在System.Threading中的包含了下表中的多个常用委托,其中ThreadStart、ParameterizedThreadStart是最常用到的委托。由Threadstart生成的线程是最直接的方式,但由Threadstart所生成并不受线程池管理。而ParameterizedThreadStart是为异步触发带参数的方法而设的,在下ーー节将为大家逐一细说。
委托说明Contextcallback表示要在新上下文中调用的方法。ParameterizedThreadStart表示在Thread上执行的方法。ThreadExceptionEventHandler发示将要处理Application的ThreadException事件的方法。Threadstart表示在Thread上执行的方法。TimerCallback表示处理来自Timer的调用的方法。WaitCallback表示线程池线程要执行的回调方法。WaitOrTimerCallback表示当WaitHandle超时或终止时要调用的方法。线程的管理方式通过Threadstart来创建一个新线程是最直接的方法,但这样创建出来的线程比较难管理,如果创建过多的线程反而会让系统的性能下载。有见及此,.NET为线程管理专门设置了一个CLR线程池,使用CLR线程池系统可以更合理地管理线程的使用。所有请求的服务都能运行于线程池中,当运行结束时线程便会回归到线程池。通过设置,能控制线程池的最大线程数量,在请求超出线程最大值时,线程池能按照操作的优先级别来执行,让部分操作处于等待状态,待有线程回归时再执行操作。基础知识就为大家介绍到这里,下面将详细介绍多线程的开发。返回目录三、以Threadstart方式实现多线程使用ThreadStart委托这里先以ー个例子体现一下多线程带来的好处,首先在Message类中建立一个方法ShowMessage(),里面显示了当前运行线程的Id,并使用Thread.Sleep(int)方法模拟部分工作。在main()中通过Threadstart委托绑定Message对象的ShowMessage()方法,然后通过Thread.Start()执行异步方法。电publicclassMessage(publicvoidShowMessage()(stringmessage=string.Format("Asyncthreadldis:{0}",6 Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);89 for(intn=0;n<10;n++)1112Thread.Sleep(300);1112Console.WriteLine("Thenumberis:n+n.ToString());TOC\o"1-5"\h\z)}}class Program{staticvoidMain(string[]args)(Console.WriteLine("Mainthreadldis:"+Thread.CurrentThread.ManagedThreadld);Messagemessage=newMessage();Threadthread=newThread(newThreadstart(message.ShowMessage));thread.Start();TOC\o"1-5"\h\zConsole.WriteLine("Dosomething !");Console.WriteLine("Mainthreadworkingiscomplete!");28}}小请注意运行结果,在调用Thread.Start。方法后,系统以异步方式运行Message.ShowMessage(),而主线程的操作是继续执行的,在Message.ShowMessageQ完成前,主线程已完成所有的操作。3.2使用ParameterizedThreadStart委托ParameterizedThreadStart委托与Threadstart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart对应方法的参数为object,此参数可以为ー个值对象,也可以为ー个自定义对象。publicclassPersonpublicstringNameget;set;publicintAge10get;11set;121315publicclassMessage1617对象,也可以为ー个自定义对象。publicclassPersonpublicstringNameget;set;publicintAge10get;11set;121315publicclassMessage1617publicvoidShowMessage(objectperson)1819if(person!=null)2122stringmessagestring.Format("\n{0}*sageis{1}!2122stringmessagestring.Format("\n{0}*sageis{1}!\nAsyncthreadldis:{2}"z23_person.Name,_person.Age,Thread.CurrentThread.ManagedThreadld);24Console.WriteLine(message);2526for(intn=0;n<10;n++)2728Thread.Sleep(300);20Person_person=(Person)person;29Console.WriteLine("Thenumberis:"+n.ToString());293031323334classProgram34classProgram3536staticvoidMain(string[]args)373638Console.WriteLine("Mainthreadldis:"+Thread.CurrentThread.ManagedThreadld);
3839Messagemessage=newMessage();//绑定带参数的异步方法Threadthread=newThread(newParameterizedThreadStart(message.ShowMessage));Personperson=newPerson();44 person.Name="Jack";person.Age=21;thread.Start(person);//启动异步线程TOC\o"1-5"\h\z48 Console.WriteLine("Dosomething !");Console.WriteLine("Mainthreadworkingiscomplete!");50})运行结果:前台线程与后台线程注意以上两个例子都没有使用Console.ReadKey(),但系统依然会等待异步线程完成后オ会结束。这是因为使用Thread.Start()启动的线程默认为前台线程,而系统必须等待所有前台线程运行结束后,应用程序域オ会自动卸载。在第二节曾经介绍过线程Thread有一个属性IsBackground,通过把此属性设置为true,就可以把线程设置为后台线程!这时应用程序域将在主线程完成时就被卸载,而不会等待异步线程的运行。挂起线程
法。め123456789101112131415161718192021222324252627282930313233为了等待其他后台线程完成后再结束主线程,就可以使用Thread.Sleep。方publicclassMessage{publicvoidShowMessage()(stringmessage=string.Format(w\nAsyncthreadldis:{〇}",Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);for(intn=0;n<10;n++)(Thread.Sleep(300);Console.WriteLine("Thenumberis:"+n.ToString());)classProgram{staticvoidMain(string[]args)(Console.WriteLine("Mainthreadldis:"+Thread.CurrentThread.ManagedThreadld);Messagemessage=newMessage();Threadthread=newThread(newThreadstart(message.ShowMessage));thread.IsBackground=true;thread.Start();Console.WriteLine("Dosomething !");Console.WriteLine("Mainthreadworkingiscomplete!");Console.WriteLine("Mainthreadsleep!");Thread.Sleep(5000);埼运行结果如下,此时应用程序域将在主线程运行5秒后自动结束
但系统无法预知异步线程需要运行的时间,所以用通过Thread.Sleep(int)阻塞主线程并不是-•个好的解决方法。有见及此,.NET专门为等待异步线程完成开发了另一个方法thread.Join。。把上面例子中的最后一行Thread.Sleep(5000)修改为thread.Join()—能保证主线程在异步线程thread运行结亲后才会终止。Suspend与Resume(慎用)Thread.Suspend()与Thread.Resume()是在Framework").0就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend〇使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。终止线程若想终止正在运行的线程,可以使用Abort()方法。在使用Abort〇的时候,将引发,ー个特殊异常ThreadAbortException〇若想在线程终止前恢复线程的执行,可以在捕获异常后,在catch(ThreadAbortExceptionex){...}中调用Thread.ResetAbort()取消终止。而使用Thread.Join〇可以保证应用程序域等待异步线程结束后オ终止运行。电staticvoidMain(string[]args){Console.WriteLine("Mainthreadldis:"+Thread.CurrentThread.ManagedThreadld);Threadthread=newThread(newThreadstart(AsyncThread));thread.IsBackground=true;
10111213141516171819202122232425262728293031323334353637mberis380));394041424344454647thread.Start();thread.Join();//以异步方式调用staticvoidAsyncThread()(try(stringmessage=string.Format(**\nAsyncthreadldis:{〇}",Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);for(intn=0;n<10;n++)(//当n等于4时,终止线程if(n>=4)(Thread.CurrentThread.Abort(n);)Thread.Sleep(300);Console.WriteLine("Thenumberis:"+n.ToString());)}catch(ThreadAbortExceptionex)(〃输出终止线程时n的值if(ex.Exceptionstate!=null)Console.WriteLine(string.Format("Threadabortwhenthenu:{〇}!”,ex.Exceptionstate.ToString//取消终止,继续执行线程Thread.ResetAbort();Console.WriteLine("ThreadResetAbort!");)//线程结束Console.WriteLine("ThreadClose!");运行结果如下
返回目录四、CLR线程池的工作者线程关于CLR线程池使用Threadstart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。有见及此,.NET引入CLR线程池这个概念。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池オ初始化ー个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。注意、通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal〇工作者线程与I/〇线程CLR线程池分为工作者线程(workerThreads)与I/O线程(completionPortThreads)两种,工作者线程是主要用作管理CLR内部对象的运作,I/O(Input/Output)线程顾名思义是用于与外部系统交换信息,I〇线程的细节将在下ー节详细说明。通过ThreadPool.GetMax(outintworkerThreads,outintcompletionPortThreads)和ThreadPool.SetMax(intworkerThreads,intcompletionPortThreads)两个方法可以分别读取和设置CLR线程池中エ作者线程与I/O线程的最大线程数。在Framework2.0中最大线程默认为25*CPU数,在Framewok3.0、4.0中最大线程数默认为250・CPU数,在近年13,15,17CPU出现后,线程池的最大值一般默认为1000、2000o若想测试线程池中有多少的线程正在投入使用,可以通过ThreadPool.GetAvailableThreads(outintworkerThreads,outintcompletionPortThreads)方法。
使用CLR线程池的工作者线程一一般有两种方式,•是直接通过ThreadPool.QueueUserWorkltem()方法,二是通过委托,下面将逐一细说。通过QueueUserWorkltem启动工作者线程ThreadPool线程池中包含有两个静态方法可以直接启动工作者线程:ー为ThreadPool.QueueUserWorkItem(WaitCallback)二为ThreadPool.QueueUserWorkltem(WaitCallback,Object)先把WaitCallback委托指向ー个带有Object参数的无返冋值方法,再使用ThreadPool.QueueUserWorkltem(WaitCallback)就可以异步启动此方法,此时异步方法的参数被视为null。〇classProgram{staticvoidMain(string[]args)(〃把CLR线程池的最大值设置为1000ThreadPool.SetMaxThreads(1000, 1000);//显示主线程启动时线程池信息ThreadMessage("Start");//启动工作者线程ThreadPool.QueueUserWorkltem(newWaitCallback(AsyncCallback));Console.ReadKey();TOC\o"1-5"\h\z}static void AsyncCallback(objectstate){Thread.Sleep(200);ThreadMessage("AsyncCallback");Console.WriteLine("Asyncthreaddowork!");}〃显示线程现状staticvoidThreadMessage(stringdata){stringmessage=string.Format("{0}\nCurrentThreadldis{1}",data,Thread.CurrentThread.ManagedThreadld);26 Console.WriteLine(message);})〇运行结果使用ThreadPool.QueuellserWorkitem(WaitCallback,Object)方法可以把object对象作为参数传送到回调函数中。下面例子中就是把ー个string对象作为参数发送到回调函数当中。电classProgram{staticvoidMain(string[]args)4 (5 //把线程池的最大值设置为10006 ThreadPool.SetMaxThreads(1000,1000);7ThreadMessage("Start");9 ThreadPoo!,QueueUserWorkltem(newWaitCallback(AsyncCallback),"HelloElva");Console.ReadKey();11 }12static void AsyncCallback(objectstate){Thread.Sleep(200);ThreadMessage("AsyncCallback");stringdata=(string)state;Console.WriteLine("Asyncthreaddowork!\n"+data);TOC\o"1-5"\h\z)〃显示线程现状staticvoidThreadMessage(stringdata){stringmessage=string.Format("{0}\nCurrentThreadldis{1}",data,Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);}
运行结果通过ThreadPooLQueueUserWorkltem启动工作者线程虽然是方便,但WaitCallback委托指向的必须是ー个带有Object参数的无返回值方法,这无疑是一种限制。若方法需要有返回值,或者带有多个参数,这将多费周折。有见及此,.NET提供了另一种方式去建立工作者线程,那就是委托。4委托类使用CLR线程池中的工作者线程,最灵活最常用的方式就是使用委托的异步方法,在此先简单介绍一下委托类。当定义委托后,.NET就会自动创建一个代表该委托的类,下面可以用反射方式显示委托类的方法成员(对反射有兴趣的朋友可以先参考一下“.NET基础篇——反射的奥妙”)电classProgram!delegatevoidMyDelegate();4staticvoidMain(string[]args)(MyDelegatedelegatel=newMyDelegate(AsyncThread);//显示委托类的几个方法成员varmethods=delegatel.GetType().GetMethods();if(methods!=null)foreach(Methodinfoinfoinmethods)Console.WriteLine(info.Name);Console.ReadKey();)}委托类包括以下几个重要方法E]/methods {System.Reflection.Methodlnfo[13])□.methods[0]{VoidInvoke。}El/methods[l]{System.IAsyncResultBeginInvoke(System.AsyncCallback,System.Object))E)/methods[2]{VoidEndlnvoke(System.IAsyncResult))publicclassMyDelegate:MulticastDelegate(publicMyDelegate(objecttarget,intmethodPtr);4 //调用委托方法publicvirtualvoidInvoke();6 〃异步委托publicvirtuallAsyncResultBeginlnvoke(AsyncCallbackcallback,objectstate);publicvirtualvoidEndlnvoke(lAsyncResultresult);9 )电当调用Invokeい方法时,对应此委托的所有方法都会被执行。而Beginlnvoke与Endlnvoke则支持委托方法的异步调用,由Beginlnvoke启动的线程都属于CLR线程池中的工作者线程,在下面将详细说明。4.5利用Beginlnvoke与Endlnvoke完成异步委托方法首先建立一个委托对象,通过lAsyncResultBeginlnvoke(stringname,AsyncCallbackcallback,objectstate)异步调用委托方法,Beginlnvoke方法除最后的两个参数外,其它参数都是与方法参数相对应的。通过Beginlnvoke方法将返回--个实现了System.IAsyncResult接口的对象,之后就可以利用Endlnvoke(lAsyncResult)方法就可以结束异步操作,获取委托的运行结果。classProgram{delegatestringMyDelegate(stringname);staticvoidMain(string[]args)(ThreadMessage("MainThread");//建立委托MyDelegatemyDelegate=newMyDelegate(Hello);//异步调用委托,获取计算结果lAsyncResultresult=myDelegate.Beginlnvoke("Leslie",null,null);13 //完成主线程其他工作
14 15 //等待异步方法完成,调用Endエ冋〇)<〇(35丫的!^$ロ1い获取运行结果stringdata=myDelegate.Endlnvoke(result);Console.WriteLine(data);1819 Console.ReadKey();TOC\o"1-5"\h\z20 }2122 static stringHello(stringname)23 (24 ThreadMessage("AsyncThread");25 Thread.Sleep(2000); //虚拟异步工作26 return"Hello"+name;27 )2829 //显示当前线程staticvoidThreadMessage(stringdata)31 {stringmessage=string.Format("{0}\nThreadldis:{1}",
data,Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);}}运行结果6善用IAsyncResuIt在以上例子中可以看见,如果在使用myDelegate.BeginInvoke后立即调用myDelegate.Endlnvoke,那在异步线程未完成工作以前主线程将处于阻塞状态,等到异步线程结束获取计算结果后,主线程才能继续工作,这明显无法展示出多线程的优势。此时可以好好利用lAsyncResult提高主线程的工作性能,IAsyncResult有以下成员:电publicinterfacelAsyncResult{objectAsyncState{get;}//objectAsyncState{get;}//获取用户定义的对象,它限定或包含关于异步操作ぬ1234567891011121314151617181920212223242526272829303132333435的信息。WailHandleAsyncWaitHandle{get;} //获取用于等待异步操作完成的WaiヒHandle。boolCompletedSynchronously{get;}//获取异步操作是否同步完成的指小。boolIsCompleted{boolIsCompleted{get;}//获取异步操作是否已完成的指示。电通过轮询方式,使用IsCompleted属性判断异步操作是否完成,这样在异步操作未完成前就可以让主线程执行另外的工作。classProgram(delegatestringMyDelegate(stringname);staticvoidMain(string[]args)(ThreadMessage("MainThread**);〃建立委托MyDelegatemyDelegate=newMyDelegate(Hello);//异步调用委托,获取计算结果lAsyncResultresult=myDelegate.BeginInvoke("Leslie",null,null);//在异步线程未完成前执行其他工作while(!result.IsCompleted){Thread.Sleep(200); //虚拟操作Console.WriteLine("Maintheaddowork!");)stringdata=myDelegate.Endlnvoke(result);Console.WriteLine(data);Console,ReadKey();)staticstringHello(stringname){ThreadMessage("AsyncThread");Thread.Sleep(2000);return"Hello**+name;}staticvoidThreadMessage(stringdata)(stringmessage=string.Format("{0}\nThreadldis:{1}**,data,Thread.CurrentThread.ManagedThreadld);
36Console.WriteLine(message);3638 )运行结果:除此以外,也可以使用WailHandle完成同样的工作,WaitHandle里面包含有ー个方法Wait〇ne(inttimeout),它可以判断委托是否完成工作,在工作未完成前主线程可以继续其他工作。运行下面代码可得到与使用IAsyncResuIt.IsCompleted同样的结果,而且更简单方便。电namespaceTest(classProgram(delegatestringMyDelegate(stringname);staticvoidMain(string[]args)(ThreadMessage("MainThread");〃建立委托MyDelegatemyDelegate=newMyDelegate(Hello);//异步调用委托,获取计算结果lAsyncResultresult=myDelegate.BeginInvoke("Leslie",null,null);1617 while(!result.AsyncWaitHandle.Waitone(200))Console.WriteLine("Maintheaddowork!M);)stringdata=myDelegate.Endlnvoke(result);Console.WriteLine(data);2324 Console.ReadKey();TOC\o"1-5"\h\z25 }2627 static stringHello(string name)28 (29 ThreadMessage("AsyncThread");Thread.Sleep(2000);31 return"Hello"+name;32 }3334 static voidThreadMessage(stringdata)35 {36 stringmessage=string.Format("{0}\nThreadldis:{1}data,Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);39 }40 }〇当要监视多个运行对象的时候,使用IAsyncResult.WaitHandle.Wait〇ne可就派不上用场了。幸好.NET为WaitHandle准备了另外两个静态方法:WaitAny(waitHandle[],int)与WaitAII(waitHandleロ,int)〇其中WaitAII在嗓待所有waitHandle完成后再返回ー个bool值。而WaitAny是等待其中一个waitHandle完成后就返回一个int,这个int是代表已完成waitHandle在waitHandle[]中的数组索引。下面就是使用WaitAII的例子,运行结果与使用IAsyncResult.lsCompleted相同。电classProgram(delegatestringMyDelegate(stringname);4staticvoidMain(string[]args)(ThreadMessage("MainThread");89 //建立委托MyDelegatemyDelegate=newMyDelegate(Hello);
12 //异步调用委托,获取计算结果lAsyncResultresult=myDelegate.BeginInvoke(HLeslie",null,null);1415 //此处可加入多个检测对象WaitHandle[]waitHandleList=newWaitHandle[]{result.AsyncWaitTOC\o"1-5"\h\zHandle, };while(!WaitHandle.WaitAll(waitHandleList,200)){Console.WriteLine("Maintheaddowork!M);)string data=myDelegate.Endlnvoke(result);Console.WriteLine(data);2324 Console.ReadKey();TOC\o"1-5"\h\z25 }2627 static stringHello(stringname)28 (29 ThreadMessage("AsyncThread");Thread.Sleep(2000);31 return"Hello"+name;32 }3334 static voidThreadMessage(stringdata)35 {36 stringmessage=string.Format("{0}\nThreadldis:{1}",data,Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);39 }40 )电4.7回调函数使用轮询方式来检测异步方法的状态非常麻烦,而且效率不高,有见及此,.NET为!AsyncResultBeginlnvoke(AsyncCallback,object)准备了一个回调函数。使用AsyncCallback就可以或定一个方法作为向调函数,回调函数必须是带参数lAsyncResult且无返回值的方法:voidAsycnCallbackMethod(lAsyncResultresult)〇在Beginlnvoke方法完成后,系统就会调用AsyncCallback所绑定的回调函数,最后回调函数中调用XXXEndlnvoke(lAsyncResultresult)就可以结束异步方法,它的返回值类型与委托的返回值一致。
classProgramdelegatestringMyDelegate(stringname);staticvoidMain(string[]args)(ThreadMessage("MainThread");〃建立委托MyDelegatemyDelegate=newMyDelegate(Hello);//异步调用委托,获取计算结果myDelegate.BeginInvoke("Leslie"rnewAsyncCallback(Completed)fn//在启动异步线程后,主线程可以继续工作而不需要等待for(intn=0;n<6;n++)Console.WriteLine("Mainthreaddowork!");Console.WriteLine("");Console.ReadKey();|staticstringHello(stringname)(ThreadMessage("AsyncThread");Thread.Sleep(2000); い模拟异步操作return"\nHello"+name;staticvoidCompleted(lAsyncResultresult)(ThreadMessage("AsyncCompleted");//获取委托对象,调用Endlnvoke方法获取运行结果AsyncResult_result=(AsyncResult)result;MyDelegatemyDelegate=(MyDelegate)_resuit.AsyncDelegate;stringdata=myDelegate.Endlnvoke(_result);Console.WriteLine(data);}staticvoidThreadMessage(stringdata)(stringmessage=string.Format("{0}\nThreadldis:{1}",data,Thread.CurrentThread.ManagedThreadld);123456789101112ull)13141516171819202122232425262728293031323334353637383940414243Console.WriteLine(message);可以看到,主线在调用Beginlnvoke方法可以继续执行其他命令,而无需再等待了,这无疑比使用轮询方式判断异步方法是否完成更有优势。在异步方法执行完成后将会调用AsyncCallback所绑定的冋调函数,注意一点,回调函数依然是在异步线程中执行,这样就不会影响主线程的运行,这也使用回调函数最值得青味的地方。在回调函数中有一个既定的参数IAsyncResult,把lAsyncResult强制转换为AsyncResult后,就可以通过AsyncResult.AsyncDelegate获取原委托,再使用Endlnvoke方法获取计算结果。运行结果如下:如果想为回调函数传送・些外部信息,就可以利用Beginlnvoke(AsyncCallback,object)的最后ー个参数object,它允许外部向回调函数输入任何类型的参数。只需要在回调函数中利用AsyncResult.AsyncState就可以获取object对象。classProgramTOC\o"1-5"\h\z(public class Person{publicstringName;publicintAge;}delegatestringMyDelegate(stringname);11staticvoidMain(string[]args)1112131415161718192021222324erson)2526272829303132333435363738394041424344454647484950510;5253ThreadMessage("MainThread");〃建立委托MyDelegatemyDelegate=newMyDelegate(Hello);//建立Person对象Personperson=newPerson();person.Name="Elva";person.Age=27;//异步调用委托,输入参数对象person,获取计算结果myDelegate.BeginInvoke("Leslie",newAsyncCallback(Completed),p//在启动异步线程后,主线程可以继续工作而不需要等待for(intn=0;n<6;n++)Console.WriteLine("Mainthreaddowork!");Console.WriteLine("");Console.ReadKey();}staticstringHello(stringname)(ThreadMessage("AsyncThread");Thread.Sleep(2000);return"\nHello"+name;staticvoidCompleted(lAsyncResultresult)(ThreadMessage("AsyncCompleted");〃获取委托对象,调用Endinvoke方法获取运行结果AsyncResult_result=(AsyncResult)result;MyDelegatemyDelegate=(MyDelegate)_resuit.AsyncDelegate;stringdata=myDelegate.Endinvoke(_result);〃获取Person对象Personperson=(Person)result.AsyncState;stringmessage=person.Name+"*sageis"+person.Age.ToStringConsole.WriteLine(data+"\n"+message);
55staticvoidThreadMessage(stringdata)TOC\o"1-5"\h\z{stringmessage=string.Format(n{0}\nThreadldis:{1}n,
data,Thread.CurrentThread.ManagedThreadld);Console.WriteLine(message);))〇运行结果:五、CLR线程池的I/O线程在前ー节所介绍的线程都属于CLR线程池的工作者线程,这ー节开始为大家介绍一下CLR线程池的I/O线程I/O线程是.NET专为访问外部资源所设置的ー种线程,因为访问外部资源常常要受到外界因素的影响,为了防止让主线程受影响而长期处于阻塞状态,.NET为多个I/O操作都建立起了异步方法,例如:FileStream.TCP/IP,WebRequest,WebService等等,而且每个异步方法的使用方式都非常类似,都是以BeginXXX为开始,以EndXXX结束,下面为大家ー•解说。异步读写FileStream需要在FileStream异步调用I/O线程,必须使用以下构造函数建立FileStream对象,并把useAsync设置为true。FileStreamstream=newFileStream(stringpath,FileModemode,FileAccessaccess,FileShareshare,intbufferSize,booluseAsync);其中path是文件的相对路径或绝对路径;mode确定如何打开或创建文件;access确定访问文件的方式;share确定文件如何进程共享;bufferSize是代表缓冲区大小,--般默认最小值为8,在启动异步读取或写入时,文件大小一般大于缓冲大小;userAsync代表是否后动异步I/O线程。注意:当使用BeginRead BeginWrite方法在执行大量读或写时效果更好,但对于少量的读/写,这些方法速度可能比同步读取还要慢,因为进行线程间的切换需要大量时间。异步写入FileStream中包含BeginWrite、EndWrite方法可以启动I/O线程进行异步写入。publicoverrideIAsyncResultBeginWrite(byte[]array,intoffset,intnumBytes,AsyncCallbackuserCallback,ObjectstateObject)publicoverridevoidEndWrite(IAsyncResultasyncResult)BeginWrite返回值为lAsyncResult,使用方式与委托的Beginlnvoke方法相似,最好就是使用回调函数,避免线程阻塞。在最后两个参数中,参数AsyncCallback用于绑定回调函数;参数。bject用于传递外部数据。要注意一点:AsyncCallback所绑定的回调函数必须是带单个IAsyncResult参数的无返回值方法。在例子中,把FileStream作为外部数据传递到回调函数当中,然后在回调函数中利用IAsyncResu11.AsyncState获取FileStreamヌ寸象,最后通过FileStream.EndWrite(IAsyncResult)结束写入。电classProgram(staticvoidMain(string[]args)(//把线程池的最大值设置为1000ThreadPool.SetMaxThreads(1000,1000);ThreadPooIMessage("Start");〃新立文件File.sourFileStreamstream=newFileStream("File.sour",FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.ReadWrite,1024,true);byte[]bytes=newbyte[16384];stringmessage="Anoperating-systemThreadldhasnofixedrelationship ";bytes=Encoding.Unicode.GetBytes(message);//启动异步写入stream.BeginWrite(bytes,〇,(int)bytes.Length,newAsyncCallback(Callback),stream);stream.Flush();
Console.ReadKey();}static void Callback(lAsyncResult result){//显示线程池现状Thread.Sleep(200);ThreadPoolMessage("AsyncCallback");〃结束异步写入Filestreamstream =(Filestream)result.AsyncState;stream.EndWrite(result);stream.Close();}/ノ显示线程池现状staticvoidThreadPoolMessage(stringdata){inta,b;ThreadPool.GetAvailableThreads(outa,outb);39 stringmessage=string.Format(n{0}\nCurrentThreadldis{1}\n"+40 "WorkerThreadsis:{2}CompletionportThreadsis:{3}",data,Thread.CurrentThread.ManagedThreadld,a.ToString(),b.ToString());Console.WriteLine(message);))め由输出结果可以看到,在使用FileStream.BeginWrite方法后,系统将自动启动CLR线程池中I/O线程。5.1.2异步读取FileStream中包含BeginRead与EndRead可以异步调用I/O线程进行读取。publicoverrideIAsyncResultBeginRead(byte[]arrayjntoffset,intnumBytes,AsyncCallbackuserCallback,ObjectstateObject)publicoverrideintEndRead(IAsyncResultasyncResult)其使用方式与BeginWrite和EndWrite相似,AsyncCallback用于绑定回调函数;Object用于传递外部数据。在回调函数只需要使用lAsyncResut.AsyncState就可获取外部数据。EndWrite方法会返回从流读取到的字节数量。首先定义FileData类,里面包含FileStream对象,byte[]数组和长度。然后把FileData对象作为外部数据传到回调函数,在回调函数中,把lAsyncResult.AsyncState强制转换为RleData»然后通过FileStream.EndRead(IAsyncResult)结束读取。最后比较一下长度,若读取到的长度与输入的数据长度不一至,则抛出异常。.classProgramTOC\o"1-5"\h\z(publicclassFileData(publicFileStreamStream;publicintLength;publicbyte[]ByteData;)static voidMain(string[]args){//把线程池的最大值设置为1000ThreadPool.SetMaxThreads(1000,1000);ThreadPoolMessage("Start");ReadFile();Console.ReadKey();)static voidReadFile(){22 byte[]byteData=newbyte[80961024];FileStreamstream=newFileStream("Filel.sour",FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.ReadWrite,1024,true);//把FileStream对象,byte[]对象,长度等有关数据绑定到FileData对象中,以附带属性方式送到回调函数FileDatafileData=newFileData();28fileData.Stream=stream;
2829 fileData.Length=(int)stream.Length;fileData.ByteData=byteData;3132 //启动异步读取stream.BeginRead(byteData,〇,fileData.Length,newAsyncCallback(Completed),fileData);34 )3536 static voidCompleted(lAsyncResultresult)37 (ThreadPoolMessage("Completed**);39//把AsyncResult.AsyncState转换为FileData对象,以FileStream.EndRead完成异步读取FileDatafileData=(FileData)result.AsyncState;intlength=fileData.Stream.EndRead(result);43 fileData.Stream.Close();44//如果读取到的长度与输入长度不一致,则抛出异常if(length!=fileData.Length)thrownewException("Streamisnotcomplete!**);4849 stringdata=Encoding.ASCII.GetString(fileData.ByteData,0,fileData.Length);Console.WriteLine(data.Substring(2,22));51 }5253 〃显示线程池现状54 staticvoidThreadPoolMessage(stringdata)55 {56 inta,b;ThreadPool.GetAvailableThreads(outa,outb);stringmessage=string.Format("+59 "WorkerThreadsis:{260 data,Thread.Currentng(),b.ToString());61 Console.WriteLine(message);62 )6364 }"{0}\nCurrentThreadldis{1}\n}CompletionPortThreadsis:{3}**,Thread.ManagedThreadld,a.ToStri由输出结果可以看・到,在使用FileStream.BeginRead方法后,系统将自动启动CLR线程池中I/O线程。
注意:如果你看到的测试结果正好相反:工作者线程为999,"〇线程为100〇,这是因为FileStream的文件容量小于缓冲值1024所致的。此时文件将会一次性读取或写入,而系统将启动工作者线程而非!/0线程来处理回调函数。5.2异步操作TCP/IP套接字在介绍TCP/IP套接字前先简单介绍ー下Networkstream类,它是用于网络访问的基础数据流。Networkstream提供了好几个方法控制套接字数据的发送与接收,其中BeginRead、EndRead>BeginWrite、EndWrite能够实现异步操作,而且异步线程是来自于CLR线程池的I/O线程。publicoverrideintReadByte()publicoverrideintRead(byte[]buffer,intoffset,intsize)publicoverridevoidWriteByte(bytevalue)publicoverridevoidWrite(byte[]buffer,intoffset,intsize)publicoverridelAsyncResultBeginRead(byte[]buffer,intoffset,intsize,AsyncCallbackcallback,Objectstate)publicoverrideintEndRead(IAsyncResuItresult)publicoverridelAsyncResultBeginWrite(byte[]buffer,intoffset,intsize,AsyncCallbackcallback,Objectstate)publicoverridevoidEndWrite(lAsyncResultresult)若要创建Networkstream»必须提供已连接的Socket〇而在.NET中使用TCP/IP套接字不需要直接与Socket打交道,因为.NET把Socket的大部分操作都放在System.Net.TcpListener和System.Net.Sockets.TcpCIient里面,这两个类大大地简化了Socket的操作。一般套接字对象Socket包含一个Accept()方法,此方法能产生阻塞来等待客户端的请求,而在TcpListener类里也包含了一个相似的方法publicTcpCIientAcceptTcpCIient〇用于等待客户端的请求。此方法将会返回ー个TcpCIient对象,通过TcpCIient的publicNetworkstream
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024矿山开采渣土砂石外运及环保处理合同
- 2024年项目工程专项技术咨询合同范本版B版
- 2024年道路货物运输服务协议版B版
- 2024石材资源开发与保护合作合同范本3篇
- 2024青岛汽车租赁合同违约责任条款3篇
- 2024年高效工业设备购销合同
- 2024版广告投放合同详细条款
- 2024年无子离婚双方共识合同范本
- 2024年高层住宅工程总包合同样本
- 2024男方债务分担与子女抚养权及赡养费支付协议书9篇
- 《中国糖尿病防治指南(2024版)》更新要点解读
- 大学物理(二)知到智慧树章节测试课后答案2024年秋湖南大学
- 银行运营集中规划
- 《数据分析你懂的》课件
- TSGD7002-2023-压力管道元件型式试验规则
- 《铁路危险货物运输管理规则》
- 2024年托管装修责任协议
- 国家自然科学基金申请书模板三篇
- (医学课件)护理人文关怀
- 数据采集服务委托合同
- DB11T 1833-2021 建筑工程施工安全操作规程
评论
0/150
提交评论