.net课程系列C#高级编程.NET方向第二学期课程第六章异步与多线程编程Newture新程教育异步编程进程与线程BeginInvoke和EndInvokeIAsyncResult接口和AsyncResult类异步编程的4种方法多线程编程Thread类使用线程池线程同步死锁本章目录平时我们会用到打印、下载等操作,这类型的操作比较费时,在程序中调用这类比较费时的代码时,调用方如果停在那里等待费时的代码执行完毕,无疑会严重影响程序的可操作性。现在很多应用程序将设置保存在配置文件中,那么当程序启动时由于需要加载配置,然后利用这些配置数据进行一系列的初始化操作,但因为I/O读取操作稍慢,这将导致程序的主窗体不能立刻显示,给用户一种启动过程十分漫长的感觉,用户体验不好。对于这类问题我们怎么去解决?这类问题,可以借助异步调用或者多线程编程模型轻松解决。引言如下是可能的解决方案:把整个初始化处理放进一个单独线程,主线程启动此线程后继续执行其他操作。例如窗体绘制操作,当初始化配置数据的进行还在执行的同时,主窗体也快速的展现在用户眼前。虽然当前主窗体可能还不能完全可用,但给用户一种程序飞快运行的感觉。配置信息初始化线程此刻也在同步执行,将配置文件中的数据读取到内存,并根据配置对当前程序进行初始化。这就是本章将要讨论的异步编程和多线程编程。引言什么是进程?在启动一个应用程序后,系统将会给它分配一定的内存以及其他的一些资源,这些划定的内存以及资源的物理分隔叫做进程。在Windows系统中,可以通过“任务管理器”来查看当前运行的进程。可以看出,每个进程都包含一定数量的线程,例如360Tray.exe有53个线程。什么是线程?线程是系统分配处理器时间资源的基本单元,或者说是进程之内的独立执行的一个单元,对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。从另一个角度来说,线程是由进程创建的,由处理器使用的一个执行序列。进程与线程仔细观察“任务管理器”,会发现“应用程序”和“进程”分属两个不同的选项卡,这说明它们是不同的。一个应用程序可能包含一个或多个进程,每个进程都拥有自己独立的数据、执行代码以及系统资源。进程与线程理解进程和线程是进行异步编程的基础。我们之前使用的都是同步编程,什么是同步编程?同步编程指从第一条语句直到最后一条语句都是顺序执行。同步编程是有缺陷的,改进的方式,就是将同步编程改为异步编程,什么是异步编程?异步编程就是合理地利用多线程处理,从理论上讲,这些线程是“同时”执行的。进程与线程在C#中使用线程的方法很多,使用委托的BeginInvoke和EndInvoke方法就是其中之一。BeginInvoke方法可以使用线程异步地执行委托所指向的方法。(委托所代理的目标方法只能为1个)然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。BeginInvoke和EndInvokeclassProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args){PrintDelegateprintDelegate=Program.Print;IAsyncResultresult=printDelegate.BeginInvoke(helloworld,null,null);printDelegate.EndInvoke(result);}publicstaticvoidPrint(stringcontent){Console.WriteLine(打印中……\n+content);System.Threading.Thread.Sleep(2000);}}IAsyncResult接口定义异步操作的状态,BeginInvoke方法的返回类型以及EndInvoke方法的参数均为IAsyncResult接口,以下是源代码:IAsyncResult接口和AsyncResult类publicinterfaceIAsyncResult{objectAsyncState{get;}WaitHandleAsyncWaitHandle{get;}boolCompletedSynchronously{get;}boolIsCompleted{get;}}属性返回类型说明AsyncStateobject返回一个对象,是启动异步操作的方法的最后一个参数AsyncWaitHandleWaitHandle获取用于等待异步操作完成的WaitHandleCompletedSynchronouslybool获取一个值,指示异步操作是否同步完成IsCompletedbool获取一个值,指示异步操作是否已完成AsyncResult类实现了IAsyncResult接口,主要作用是封装了通过BeginInvoke进行异步调用的执行结果。BeginInvoke并非直接返回AsyncResult类型,而是返回IAsyncResult接口,但返回值可以显式地转换为AsyncResult类型。AsyncResult类有一个属性AsyncDelegate,该属性保存的是在其上进行异步调用的委托对象,只不过该属性返回的是object类型,需要显式转换为具体的委托类型。IAsyncResult接口和AsyncResult类第一种:使用EndInvoke当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke返回就会一直阻塞,直到被调用的方法执行完毕。异步编程的4种方法classProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args){intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine(主线程Id:+threadId+\t+打印方法开始调用);IAsyncResultresult=printDelegate.BeginInvoke(helloworld,null,null);printDelegate.EndInvoke(result);}publicstaticvoidPrint(stringcontent){intthreadId=Thread.CurrentThread.ManagedThreadId;Console.WriteLine(当前线程Id:+threadId);Console.WriteLine(打印中……\n+content);Thread.Sleep(2000);Console.WriteLine(当前线程Id:+threadId+\t+打印方法调用完毕);}}第二种:使用WaitHandle使用WaitHandle类型的WaitOne方法,WaitOne有5个重载:•boolWaitOne()•boolWaitOne(intmillisecondsTimeout)•boolWaitOne(TimeSpantimeout)•boolWaitOne(intmillisecondsTimeout,boolexitContext)•boolWaitOne(TimeSpantimeout,boolexitContext)其中,第一个不带任何参数的重载实际是对WaitOne(-1,false)方法的调用•第一个参数表示等待的毫秒数,值-1表示无期限等待。•第二个参数表示在等待前是否退出上下文的同步域,并在稍后重新获取。第4个和第5个重载实际相同,第5个重载会将TimeSpan类型转换为毫秒数。这些重载的核心实现为第4个重载,其他的都在其基础上实现。异步编程的4种方法异步编程的4种方法classProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args){intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine(主线程Id:+threadId+\t+打印方法开始调用);IAsyncResultresult=printDelegate.BeginInvoke(helloworld,null,null);result.AsyncWaitHandle.WaitOne(5000,false);}}第三种:轮询之前两种方法中,只能等待异步方法执行完毕,在完毕之前没有任何提示信息,整个程序就像没有反应一样,用户体验不好。可以通过IAsyncResult类的的IsCompleted属性来检查异步调用是否完成了,如果没有完成,则可以适时地显示一些提示信息,提升用户体验。异步编程的4种方法classProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args){intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine(主线程Id:+threadId+\t+打印方法开始调用);IAsyncResultresult=printDelegate.BeginInvoke(helloworld,null,null);while(!result.IsCompleted){Console.WriteLine(.);Thread.Sleep(500);}}}第四种:回调之前三种方法都是在等待异步方法执行完毕后才能拿到执行的结果,期间主线程均处于等待状态。回调和它们最大的区别是,在调用BeginInvoke时只要提供了回调方法,那么主线程就不必再等待异步线程工作完毕,异步线程在工作结束后会主动调用提供的回调方法,并在回调方法中做相应的处理,例如显示异步调用的结果,等等。回顾:IAsyncResultresult=printDelegate.BeginInvoke(helloworld,null,null);第一个参数表示委托签名中的参数第二个参数AsyncCallback,表示回调方法第三个参数object@object,给回调方法传递一些值,一般可以传递被调用方法的委托。异步编程的4种方法异步编程的4种方法classProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args){intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine(主线程Id:+threadId+\t+打印方法开始调用);IAsyncResultresult=printDelegate.BeginInvoke(helloworld,PrintComplete,printDelegate);Thread.Sleep(10000);}publicstaticvoidPrintComplete(IAsyncResultresult){intthreadId=Thread.CurrentThread.ManagedThreadId;(result.AsyncStateas