异步编程(二)基于事件的异步编程模式 (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组件可以很轻松的实现异步处理,并且该组件还支持事件的取消.进度报告等功能.本文以计 ...
随机推荐
- Py基础+中级
原文 Py学习博客 1:https://www.cnblogs.com/fu-yong/p/8060198.html2:while True:just do it 三.Python的默认编码 ▷pyt ...
- Swift - 关键字(typealias、associatedtype)
Typealias typealias 是用来为已经存在的类型重新定义名字的,通过命名,可以使代码变得更加清晰.使用的语法也很简单,使用typealias 关键字像使用普通的赋值语句一样,可以将某个已 ...
- gulp打包压缩代码以及图片
1.首先全局安装gulp 全局安装就不做介绍了 初学gulp,终于把常用的配置,api,语法弄明白了! gulp插件地址:http://gulpjs.com/plugins gulp官方网址:http ...
- 如何修改wifi为家庭网络
一不小心手快,把新链接的 wifi 选择成“公用网络”了,使用过程中导致某些应用无法联网,那个恨呐!!! 幸好,咱们可以进行手工更改,哈哈,跟哥一起来操作: 进入”网络与共享中心界面": 选 ...
- [JSOI2018]战争(闵可夫斯基和)
害怕,可怜几何题 果然不会 题目就是说给你两个凸包,每次询问给你一个向量 \(c\) 问你能不能从两个凸包 \(A\) , \(B\) 里分别找到一个点 \(a\) , \(b\) 满足 \(a+c= ...
- 洛谷P1909 买铅笔
题目描述 P老师需要去商店买n支铅笔作为小朋友们参加NOIP的礼物.她发现商店一共有 333 种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同.为了公平起 见,P老师决定只买同一种包装的 ...
- nyoj14-会场安排问题
会场安排问题 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办.小刘的工作就 ...
- hdu 4826
hdu 4826 题意 度度熊是一只喜欢探险的熊,一次偶然落进了一个 $ m * n $ 矩阵的迷宫,该迷宫只能从矩阵左上角第一个方格开始走,只有走到右上角的第一个格子才算走出迷宫,每一次只能走一格, ...
- flask_sqlalchemy和sqlalchemy联系区别及其使用方式
### 使用SQLAlchemy去连接数据库: 1.使用SQLALchemy去连接数据库,需要使用一些配置信息,然后将他们组合成满足条件的字符串:HOSTNAME = '127.0.0.1'PORT ...
- docker 私有仓库的两种方式
1.使用官方默认的registry镜像构建本地仓库 这种方式适用于小规模的镜像仓库储存,没有Ui界面 (1)docker pull registry (2)docker run -d -p 5000: ...