1、引言html
在上一个专题中为你们介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为咱们实现异步编程提供了必定的支持,同时它也存在着一些明显的问题——不支持对异步操做的取消和没有提供对进度报告的功能,对于有界面的应用程序来讲,进度报告和取消操做的支持也是必不可少的,既然存在这样的问题,微软固然也应该提供给咱们解决问题的方案了,因此微软在.NET 2.0的时候就为咱们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型——EAP。编程
实现了基于事件的异步模式的类将具备一个或者多个以Async为后缀的方法和对应的Completed事件,而且这些类都支持异步方法的取消、进度报告和报告结果。小程序
当咱们调用实现基于事件的异步模式的类的 XxxAsync方法时,即表明开始了一个异步操做,该方法调用完以后会使一个线程池线程去执行耗时的操做,因此当UI线程调用该方法时,固然也就不会堵塞UI线程了。异步
2、深刻剖析BackgroundWorker组件类async
在深刻讲解BackgroundWorker类以前,让咱们先看看BackgroundWorker类具备的成员和对应的介绍的(这里只列出一些在异步编程中常用的属性和方法,具体关于该类成员能够查看MSDN——BackgroundWorker):ide
BackgroundWorker类异步编程 |
|
公共属性函数 |
|
属性名post |
说明this |
CancellationPending |
获取一个值,指示应用程序是否已请求取消后台操做 |
IsBusy |
获取一个值,指示 BackgroundWorker是否正在运行异步操做。 |
WorkReportsProgress |
获取或设置一个值,该值指示 BackgroundWorker可否报告进度更新。 |
WorkerSupportsCancellation |
获取或设置一个值,该值指示 BackgroundWorker是否支持异步取消。 |
公共方法 |
|
名称 |
说明 |
CancelAsync |
请求取消挂起的后台操做。 |
ReportProgress |
引起 ProgressChanged 事件(官方这样解释我就要信?) |
RunWorkerAsync |
开始执行后台操做。 |
公共事件 |
|
名称 |
说明 |
DoWork |
调用 RunWorkerAsync 时发生(官方是这么解释的,你想知道为何调用RunWorkerAsync方法就会触发DoWork事件吗?) |
ProgressChanged |
调用ReportProgress时发生(官方是这么解释的,你想知道为何调用ReportProgress方法就会触发ProgressChanged事件吗?) |
RunWorkerCompleted |
当后台操做已完成、被取消或引起异常时发生。 |
分析为何调用RunWorkerAsync方法就会触发DoWorker事件?
// RunWorkerAsync的源码什么都没有作,只是调用了该方法的重载方法RunWorkerAsync(objectargument)方法 publicvoidRunWorkerAsync() { this.RunWorkerAsync(null); }
// 下面就看看RunWorkerAsync带有一个参数的重载方法的源码 publicvoidRunWorkerAsync(object argument) { if (this.isRunning) { thrownewInvalidOperationException(SR.GetString("BackgroundWorker_WorkerAlreadyRunning")); } //这个方法把一些私有字段赋值 //这些赋值是为了咱们使用isBusy公共属性来检查BackgroundWorker组件是否在运行异步操做 //和检查公共属性 CancellationPending属性来检查异步操做是否取消 this.isRunning=true; this.cancellationPending=false; //AsyncOperation类是经过得到调用线程的同步上下文来实现跨线程访问,这个实如今APM专题中咱们是本身经过代码来实现的,然而实现EAP的类在内容帮咱们实现了,这样就不须要咱们本身去解决这个问题了,从中也能够看出EAP的实现是基于APM的,只是实现EAP的类帮咱们作了更多的背后的事情 this.asyncOperation= AsyncOperationManager.CreateOperation(null); //这里就是咱们上一专题中介绍的使用委托实现的异步编程部分 // 咱们在EAP的类中调用了BeginInvoke方法,从而也能够证实EAP是基于APM的,因此APM的介绍颇有必要。 this.threadStart.BeginInvoke(argument,null,null); }
1. 咱们从上面的代码能够看到调用RunWorkerAsync方法就是调用threadStart委托,咱们要知道RunWorkerAsync方法到底背后发生了什么事情,就首先须要知道threadStart委托包装了哪一个方法?而且须要知道委托在什么地方实例化的? 2. 委托什么地方实例化话的?谈到实例化固然你们首先想到的就是构造函数了,不错,咱们就看看BackgroundWorker构造函数: // 这里查看构造函数都是由于前面的分析 // 从构造函数中咱们能够确实能够看到threadStart委托是这里初始化的 public BackgroundWorker() { // 初始化threadStart委托 this.threadStart=new WorkerThreadStartDelegate(this.WorkerThreadStart); // 这里也初始化了操做完成委托和进度报告委托 this.operationCompleted= new SendOrPostCallback(this.AsyncOperationCompleted); this.progressReporter=new SendOrPostCallback(this.ProgressReporter); } 3. 从构造函数中已经知道threadStart包装了WorkerThreadStart方法,从而解决了第一步的疑惑,接下来就让咱们看看WorkerThreadStart方法的代码: privatevoidWorkerThreadStart(object argument) { objectresult =null; Exceptionerror =null; boolcancelled =false; try { DoWorkEventArgs e =newDoWorkEventArgs(argument); //该方法中又是调用了onDoWork方法 // this.OnDoWork(e); if(e.Cancel) { cancelled =true; } else { result = e.Result; } } catch(Exception exception2) { error= exception2; } //这里也解释了操做完成时会触发Completed事件 // 分析过程和调用RunWorkerAsync方法触发DoWork事件相似 RunWorkerCompletedEventArgs arg =newRunWorkerCompletedEventArgs(result, error, cancelled); this.asyncOperation.PostOperationCompleted(this.operationCompleted,arg); } 4. 上面的代码中能够知道WorkerThreadStart调用了受保护的OnDoWork方法,下面就让咱们看看OnDoWork方法的代码,到这里咱们离事物的本质已经不远了。 // OnDoWork的源码 protectedvirtualvoidOnDoWork(DoWorkEventArgs e) { //从事件集合中得到委托对象 DoWorkEventHandler handler = (DoWorkEventHandler)base.Events[doWorkKey]; if(handler !=null) { //调用委托,也就是调用注册DoWork事件的方法 // 咱们在使用BackgroundWorker对象的时候,首先须要对它的DoWork事件进行注册 //到这里就能够解释为何调用RunWorkerAsync方法会触发DoWork事件了 handler(this, e); } } // 当咱们使用+=符号对DoWork事件进行注册时,背后调用确实Add方法,具体能够查看个人事件专题。 publiceventDoWorkEventHandler DoWork { add { //把注册的方法名添加进一个事件集合中 // 这个事件集合也是相似一个字典,doWorkKey是注册方法的key,经过这个key就能够得到包装注册方法的委托 base.Events.AddHandler(doWorkKey,value); } remove { base.Events.RemoveHandler(doWorkKey,value); } } 从上面的代码中的注释咱们能够解释一开始的疑惑,而且也更好地解释了事件特性,关于事件,你也能够参看个人事件专题(事件也是委托,归根究底又是委托啊,从而可见委委托是多么的重要,同时建议你们在理解委托的时候,能够根据后面的特性重复地去理解)。 对于开始表格中提出的其余的几个疑惑的分析思路和这个分析思路相似,你们能够按照这个思路本身去深刻理解下BackgroundWorker类,这里我就很少解释了。相信你们经过上面个人分析能够很快解决其余几个疑惑的,若是你彻底理解上面的分析相信你会对EAP,委托和事件又有进一步的理解。 4、使用BackgroundWorker组件进行异步编程 剖析完了BackgroundWorker组件以后,咱们是否是很想看看如何使用这个类来实现异步编程呢?下面向你们演示一个使用BackgroundWorker组件实现异步下载文件的一个小程序,该程序支持异步下载(指的就是用线程池线程要执行下载操做),断点续传、下载取消和进度报告的功能,经过这个程序,相信你们也会对基于事件的异步模式有一个更好的理解和知道该模式能够完成一些什么样的任务,下面就看看该程序的主要代码的(由于代码中都有详细的解释,这里就很少解释代码的实现了): //Begin Start Download file or Resume the download privatevoidbtnDownload_Click(object sender, EventArgs e) { if(bgWorkerFileDownload.IsBusy !=true) { // Start the asynchronous operation // Fire DoWork Event bgWorkerFileDownload.RunWorkerAsync(); // Create an instance of the RequestState requestState =newRequestState(downloadPath); requestState.filestream.Seek(DownloadSize, SeekOrigin.Begin); this.btnDownload.Enabled =false; this.btnPause.Enabled =true; } else { MessageBox.Show("正在执行操做,请稍后"); } } //Pause Download privatevoidbtnPause_Click(object sender, EventArgs e) { if(bgWorkerFileDownload.IsBusy&&bgWorkerFileDownload.WorkerSupportsCancellation==true) { // Pause the asynchronous operation // Fire RunWorkerCompleted event bgWorkerFileDownload.CancelAsync(); } } #regionBackGroundWorker Event //Occurs when RunWorkerAsync is called. privatevoidbgWorkerFileDownload_DoWork(object sender,DoWorkEventArgs e) { // Getthe source of event BackgroundWorker bgworker = senderasBackgroundWorker; try { // Do the DownLoad operation // Initialize an HttpWebRequest object HttpWebRequest myHttpWebRequest =(HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim()); // If the part of the file have been downloaded, // The server should start sending data from the DownloadSize to the endof the data in the HTTP entity. if (DownloadSize !=0) { myHttpWebRequest.AddRange(DownloadSize); } // assign HttpWebRequest instance to its request field. requestState.request = myHttpWebRequest; requestState.response =(HttpWebResponse)myHttpWebRequest.GetResponse(); requestState.streamResponse = requestState.response.GetResponseStream(); int readSize =0; while (true) { if (bgworker.CancellationPending ==true) { e.Cancel = true; break; } readSize = requestState.streamResponse.Read(requestState.BufferRead,0,requestState.BufferRead.Length); if (readSize >0) { DownloadSize +=readSize; intpercentComplete = (int)((float)DownloadSize/ (float)totalSize * 100); requestState.filestream.Write(requestState.BufferRead,0,readSize); // 报告进度,引起ProgressChanged事件的发生 bgworker.ReportProgress(percentComplete); } else { break; } } } catch { throw; } } //Occurs when ReportProgress is called. privatevoidbgWorkerFileDownload_ProgressChanged(object sender,ProgressChangedEventArgs e) { this.progressBar1.Value= e.ProgressPercentage; } //Occurs when the background operation has completed, has been canceled, or hasraised an exception. privatevoidbgWorkerFileDownload_RunWorkerCompleted(objectsender, RunWorkerCompletedEventArgs e) { if(e.Error !=null) { MessageBox.Show(e.Error.Message); requestState.response.Close(); } elseif(e.Cancelled) { MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}\n已经下载的字节数为: {1}字节",downloadPath, DownloadSize)); requestState.response.Close(); requestState.filestream.Close(); this.btnDownload.Enabled =true; this.btnPause.Enabled =false; } else { MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节",downloadPath, totalSize)); this.btnDownload.Enabled =false; this.btnPause.Enabled =false; requestState.response.Close(); requestState.filestream.Close(); } } #endregion // GetTotal Size of File privatevoidGetTotalSize() { HttpWebRequest myHttpWebRequest =(HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim()); HttpWebResponse response =(HttpWebResponse)myHttpWebRequest.GetResponse(); totalSize = response.ContentLength; response.Close(); } //This class stores the State of the request. publicclassRequestState { publicintBufferSize =2048; publicbyte[]BufferRead; publicHttpWebRequest request; publicHttpWebResponse response; publicStream streamResponse; publicFileStream filestream; publicRequestState(string downloadPath) { BufferRead =newbyte[BufferSize]; request =null; streamResponse =null; filestream =new FileStream(downloadPath,FileMode.OpenOrCreate); } }