异步编程(二)基于事件的异步编程模式 (EAP)
一、引言
在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的,既然存在这样的问题,微软当然也应该提供给我们解决问题的方案了,所以微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型——EAP。
实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步方法的取消、进度报告和报告结果。
当我们调用实现基于事件的异步模式的类的 XxxAsync方法时,即代表开始了一个异步操作,该方法调用完之后会使一个线程池线程去执行耗时的操作,所以当UI线程调用该方法时,当然也就不会堵塞UI线程了。
二、深入剖析BackgroundWorker组件类
在深入讲解BackgroundWorker类之前,让我们先看看BackgroundWorker类具有的成员和对应的介绍的(这里只列出一些在异步编程中经常使用的属性和方法,具体关于该类成员可以查看MSDN——BackgroundWorker):
|
BackgroundWorker类 |
|
|
公共属性 |
|
|
属性名 |
说明 |
|
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);
}
. 我们从上面的代码可以看到调用RunWorkerAsync方法就是调用threadStart委托,我们要知道RunWorkerAsync方法到底背后发生了什么事情,就首先需要知道threadStart委托包装了哪个方法?并且需要知道委托在什么地方实例化的?
. 委托什么地方实例化话的?谈到实例化当然大家首先想到的就是构造函数了,不错,我们就看看BackgroundWorker构造函数:
// 这里查看构造函数都是因为前面的分析
// 从构造函数中我们可以确实可以看到threadStart委托是这里初始化的
public BackgroundWorker()
{
// 初始化threadStart委托
this.threadStart=new WorkerThreadStartDelegate(this.WorkerThreadStart);
// 这里也初始化了操作完成委托和进度报告委托
this.operationCompleted= new SendOrPostCallback(this.AsyncOperationCompleted);
this.progressReporter=new SendOrPostCallback(this.ProgressReporter);
}
. 从构造函数中已经知道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);
} . 上面的代码中可以知道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,委托和事件又有进一步的理解。
四、使用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 !=)
{
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 =;
while (true)
{
if (bgworker.CancellationPending ==true)
{
e.Cancel = true;
break;
}
readSize = requestState.streamResponse.Read(requestState.BufferRead,,requestState.BufferRead.Length);
if (readSize >)
{
DownloadSize +=readSize;
intpercentComplete = (int)((float)DownloadSize/ (float)totalSize * );
requestState.filestream.Write(requestState.BufferRead,,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 =;
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);
}
}
异步编程(二)基于事件的异步编程模式 (EAP)的更多相关文章
- C#中的异步调用及异步设计模式(三)——基于事件的异步模式
四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: · ...
- C#中的异步调用及异步设计模式(二)——基于 IAsyncResult 的异步设计模式
三.基于 IAsyncResult 的异步设计模式(设计层面) IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来 ...
- 二、基于事件的异步编程模式(EAP)
一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...
- .NET - 基于事件的异步模型
注:这是大概四年前写的文章了.而且我离开.net领域也有四年多了.本来不想再发表,但是这实际上是Active Object模式在.net中的一种重要实现方法,因此我把它掏出来发布一下.如果该模型有新的 ...
- 基于事件的异步模式(EAP)
什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步 ...
- Event-based Asynchronous Pattern Overview基于事件的异步模式概览
https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...
- 在Silverlight中的DispatcherTimer的Tick中使用基于事件的异步请求
需求:在silverlight用户界面上使用计时器定时刷新数据. 在 Silverlight 中的 DispatcherTimer 的 Tick 事件 中使用异步请求数据时,会出现多次请求的问题,以下 ...
- 【温故知新】C#基于事件的异步模式(EAP)
在开发winform和调用asp.net的web service引用的时候,会出现许多命名为 MethodNameAsync 的方法. 例如: winform的按钮点击 this.button1.Cl ...
- 基于事件的异步模式——BackgroundWorker
实现异步处理的方法很多,经常用的有基于委托的方式,今天记录的是基于事件的异步模式.利用BackgroundWorker组件可以很轻松的实现异步处理,并且该组件还支持事件的取消.进度报告等功能.本文以计 ...
随机推荐
- textarea 自适应高度
试了好多方法,包括百度了好多.一旦接口获取的内容,就不好用了.有时候就是脑袋转不过来,想了好久的方法居然那么简单,然后,脑洞大开,忽然想到还可以这样弄, 很简单,两句话 var textareaHei ...
- 00--Linux常用命令大全
系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...
- 07 --C语言字符串函数
1)字符串操作 复制 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strdup(char *str) 将串拷贝到新建的位置处 ...
- 模拟一个简单的基于tcp的远程关机程序(转)
最近在学习unix网络编程,现在正在学习tcp的通信.其实,只要建立起了tcp通信,操作远端的计算机就不是什么问题了.正向telnet一样,也是基于tcp/IP协议的.所以这个实验,也算是对telne ...
- hibernate详细配置
映射配置 <!-- 映射文件: 映射一个实体类对象: 描述一个对象最终实现可以直接保存对象数据到数据库中. --> <!-- package: 要映射的对象所在的包(可选,如果不 ...
- UNIX时间转换ASP代码.txt
'参数:strTime:要转换的时 间:intTimeZone:该时间对应的时区 '返回值:strTime相对于1970年1月1日午夜0点经过的秒数 '示例:ToUnixTime("2008 ...
- OPENGL学习【一】VS2008开发OPENGL程序开发环境搭建
1.VS2008工具自行在网上下载安装,现只提供VS2008开发工具中配置OPENGL环境的详细步骤.开发包及编译工具会在下方一并放出链接. 2.打开CMake的工具,主要的配置信息如下,按照数字顺序 ...
- [SCOI2016]萌萌哒(倍增+并查集)
当区间\([a,b]\)和\([c,d]\)对应相等时. 我们把两个区间对应位置上的数所在并查集合并. 最后并查集的数量为\(num\)答案就是\(9*10^num\)因为是个数,不能有前置\(0\) ...
- Mybaitis-generator生成数据对象和时间的优化
1.本章涉及到知识点,Mybaitis-generator生成数据对象和时间,xml的引用*.properties 外部文件(在这之前必须导入了mybaitis的核心架包) A.在pom.xml的案例 ...
- [SharePoint2010开发入门经典]二、开始SPS2010开发
本章概要: 1.了解SPS2010开发要素(包括工具,平台服务,开发选项) 2.熟悉主要开发工具和部署方案 3.安装.配置.简单开发案例 4.理解网站级别的安全设置