在开发winform和调用asp.net的web service引用的时候,会出现许多命名为 MethodNameAsync 的方法。

例如:

winform的按钮点击

this.button1.Click += new System.EventHandler(this.button1_Click);

private void button1_Click(object sender, EventArgs e)
{
  //dosomething
}

这就是基于事件的异步编程模式,它实现了不影响主线程的情况下异步调用耗时方法,在完成的时候通过事件进行函数回调,一般情况下,我们都应该使用该模式来公开类的异步方法。

那什么时候需要使用IAsyncResult 模式呢?微软给出了很好的答案,见https://msdn.microsoft.com/zh-cn/library/ms228966(v=vs.110).aspx

接下来就让我们通过代码实现一个基于事件的异步模式

代码场景

我们模拟一个下载器,下载我喜爱的影片,过程中实时展示下载进度,并且在下载完成后进行提醒。

核心代码如下:

    public class Downloader
{ //声明事件参数
public class DownloadCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
private string m_result;
public DownloadCompletedEventArgs(string result, Exception error, bool cancelled, Object userState)
: base(error, cancelled, userState)
{
m_result = result;
}
public string Result
{
get
{
//只读属性在返回属性值之前应调用 RaiseExceptionIfNecessary 方法。 如果组件的异步辅助代码将某一异常指定给 Error 属性或将 Cancelled 属性设置为 true,则该属性将在客户端尝试读取它的值时引发异常。 这会防止客户端因异步操作失败而访问可能无效的属性。
RaiseExceptionIfNecessary();
return m_result;
}
}
} //声明委托
public delegate void ProgressChangedEventHandler(
ProgressChangedEventArgs e);//ProgressChangedEventArgs自带有了。 public delegate void DownloadCompletedEventHandler(object sender,
DownloadCompletedEventArgs e); //内部下载处理委托
private delegate string DownLoadHandler(string url, string name, AsyncOperation asyncOp); //声明事件
public event ProgressChangedEventHandler ProgressChanged;
public event DownloadCompletedEventHandler DownloadCompleted; //声明SendOrPostCallback委托,通过AsyncOperation.post会将这些调用正确地封送到应用程序模型的合适线程或上下文。
private SendOrPostCallback onProgressChangedDelegate;
private SendOrPostCallback onDownloadCompletedDelegate; /// <summary>
/// 构造函数
/// </summary>
public Downloader()
{
onProgressChangedDelegate = new SendOrPostCallback(onProgressChanged);
onDownloadCompletedDelegate = new SendOrPostCallback(onDownloadComplete);
} /// <summary>
/// 通过AsyncOperation调用onProgressChangedDelegate委托关联该函数,保证运行在合适线程
/// </summary>
/// <param name="state"></param>
private void onProgressChanged(object state)
{
if (ProgressChanged != null)
{
ProgressChangedEventArgs e =
state as ProgressChangedEventArgs;
ProgressChanged(e);
}
} private void onDownloadComplete(object state)
{
if (DownloadCompleted != null)
{
DownloadCompletedEventArgs e =
state as DownloadCompletedEventArgs;
DownloadCompleted(this, e);
}
} /// <summary>
/// 异步下载文件
/// </summary>
/// <param name="url"></param>
/// <param name="name"></param>
public void DownloadAsync(string url, string name)
{ //url不能为null
if (url == null)
{
throw new ArgumentNullException("url");
} //userSuppliedState 参数来唯一地标识每个调用,以便区分执行异步操作的过程中所引发的事件。
//不唯一的任务 ID 可能会导致您的实现无法正确报告进度和其他事件。 代码中应检查是否存在不唯一的任务 ID,并且在检测到不唯一的任务 ID 时引发 SystemArgumentException。
//由于我们不用监控异步操作状态,所以参数设为null
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null); //异步委托调用download,如果不想再声明DownLoadHandler委托,用Action或Fun代替也行。
DownLoadHandler dh = new DownLoadHandler(DownLoad);
dh.BeginInvoke("http://pan.baidu.com/movie.avi", "乔布斯传", asyncOp, new AsyncCallback(DownloadCallBack), asyncOp);
} private void DownloadCallBack(IAsyncResult iar)
{
AsyncResult aresult = (AsyncResult)iar;
DownLoadHandler dh = aresult.AsyncDelegate as DownLoadHandler;
string r = dh.EndInvoke(iar);
AsyncOperation ao = iar.AsyncState as AsyncOperation;
//特定任务调用此方法后,再调用其相应的 AsyncOperation 对象会引发异常。
ao.PostOperationCompleted(onDownloadCompletedDelegate, new DownloadCompletedEventArgs(r, null, false, null));
} /// <summary>
/// 提供给外部调用的同步方法
/// </summary>
/// <param name="url"></param>
/// <param name="name"></param>
/// <returns></returns>
public string DownLoad(string url, string name)
{
return DownLoad(url, name, null);
} private string DownLoad(string url, string name, AsyncOperation asyncOp)
{
//url不能为null
if (url == null)
{
throw new ArgumentNullException("url");
}
for (int i = ; i < ; i++)
{
int p = i * ;
Debug.WriteLine("执行线程:" + Thread.CurrentThread.ManagedThreadId + ",传输进度:" + p + "%");
Thread.Sleep();
//不为空则是异步
if (asyncOp != null)
{
//在适合于应用程序模型的线程或上下文中调用委托。
asyncOp.Post(onProgressChangedDelegate, new ProgressChangedEventArgs(p, null));
}
}
return name + "文件下载完成!";
} }

在客户端调用:

private async void button1_Click_1(object sender, EventArgs e)
{
Downloader downloader = new Downloader();
downloader.DownloadCompleted += downloader_DownloadCompleted;
downloader.ProgressChanged += downloader_ProgressChanged;
Debug.WriteLine("调用线程:" + Thread.CurrentThread.ManagedThreadId); //异步调用
downloader.DownloadAsync("http://baidu.com", "乔布斯传.avi"); //同步调用,UI线程卡死
//string r = downloader.DownLoad("http://baidu.com", "乔布斯传.avi");
//textBox1.AppendText(r);
} void downloader_ProgressChanged(ProgressChangedEventArgs e)
{
textBox1.AppendText(("事件回调线程:" + Thread.CurrentThread.ManagedThreadId + "下载了" + e.ProgressPercentage + "%\n"));
} void downloader_DownloadCompleted(object sender, Downloader.DownloadCompletedEventArgs e)
{
textBox1.AppendText(("事件回调线程:" + Thread.CurrentThread.ManagedThreadId + "下载完成,文件为" + e.Result + "\n"));
}

运行结果:

调用线程:9
执行线程:10,传输进度:0%
执行线程:10,传输进度:10%
执行线程:10,传输进度:20%
执行线程:10,传输进度:30%
执行线程:10,传输进度:40%
执行线程:10,传输进度:50%
执行线程:10,传输进度:60%
执行线程:10,传输进度:70%
执行线程:10,传输进度:80%
执行线程:10,传输进度:90%

总结:

1、我们通过EAP模式,实现了不影响UI情况下的异步调用,归功于AsyncOperation,避免了Control.Invoke

2、如果不在应用程序模型(包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序)下正常运行,可以免去AsyncOperation这个步骤,直接在callback通知相应event。

3、实现一个这样的类有些麻烦,如果还需要监视状态,需要一个数组维护AsyncOperation

4、微软也提供了BackgroundWorker类来执行耗时的后台操作。

5、正因为麻烦,所以.net4.0后面又推出了Task,.net4.5中更加简化,各种封装,瞬间异步,如果使用Task,我们的异步函数将变成如下几句话。

        public async void DownloadTaskAsync(string url, string name)
{
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
string r = await Task<string>.Run(() =>
{
return DownLoad(url, name, asyncOp);
});
if (DownloadCompleted != null)
{
DownloadCompletedEventArgs e = new DownloadCompletedEventArgs(r, null, false, null);
DownloadCompleted(this, e);
}
}

调用方式不变。

实现基于事件的异步模式的最佳做法,见https://msdn.microsoft.com/zh-cn/library/ms228969(v=vs.110).aspx

参考:

https://msdn.microsoft.com/zh-cn/library/ms228969(v=vs.110).aspx

https://msdn.microsoft.com/zh-cn/library/e7a34yad(v=vs.110).aspx

补充:
1、CreateOperation一定要在主线程调用,会自动设置上下文,否则上下文就会是另一个线程的。

2、这里AsyncOperation的主要作用其实就是将相应委托post到CreateOperation时的上下文执行,所以DownLoadHandler中传入asyncOp参数。BeginInvoke相当于new Thread执行,尾部参数也传入asyncOp,在callback的时候post结果。DownLoad也传入asyncOp参数,post进度。

3、对于一些简单需求,回调函数调用UI时直接判断InvokeRequired通过主线程Invoke也行。

4、如果不需要监控异步操作状态,并且需求简单,那么可以在初始化的时候直接在UI主线程声明一个全局AsyncOperation,这样统一调用者一个对象post即可。

DEMO下载地址:

链接:http://pan.baidu.com/s/1qYrb81Q 密码:xu6s

【温故知新】C#基于事件的异步模式(EAP)的更多相关文章

  1. 基于事件的异步模式(EAP)

    什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步 ...

  2. Event-based Asynchronous Pattern Overview基于事件的异步模式概览

    https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...

  3. 基于事件的异步模式——BackgroundWorker

    实现异步处理的方法很多,经常用的有基于委托的方式,今天记录的是基于事件的异步模式.利用BackgroundWorker组件可以很轻松的实现异步处理,并且该组件还支持事件的取消.进度报告等功能.本文以计 ...

  4. C#中的异步调用及异步设计模式(三)——基于事件的异步模式

    四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: ·                 ...

  5. 二、基于事件的异步编程模式(EAP)

    一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...

  6. 异步编程(二)基于事件的异步编程模式 (EAP)

    一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进 ...

  7. .NET - 基于事件的异步模型

    注:这是大概四年前写的文章了.而且我离开.net领域也有四年多了.本来不想再发表,但是这实际上是Active Object模式在.net中的一种重要实现方法,因此我把它掏出来发布一下.如果该模型有新的 ...

  8. C#基于任务的异步模式

    using System; using System.Threading; using System.Threading.Tasks; using static System.Console; //异 ...

  9. 基于Task的异步模式的定义

    返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...

随机推荐

  1. 使用word文档直接发表博客 8 )

    目前大部分的博客作者在用Word写博客这件事情上都会遇到以下3个痛点: 1.所有博客平台关闭了文档发布接口,用户无法使用Word,Windows Live Writer等工具来发布博客.使用Word写 ...

  2. List of HTTP header fields

    https://en.wikipedia.org/wiki/List_of_HTTP_header_fields Content-Type The MIME type of the body of t ...

  3. Mac上phantomjs装了不能用的解决

    下了,装了.然后整这句命令: phantomjs --version 直接报错.. 查了半天的官方文档,居然连个毛线都么找到.终于...墨迹到了一个方法,就抱着试一试的心态在terminal里输入了这 ...

  4. 简单总结:以设计模式的角度总结Java基本IO流

    在总结 Java Basic IO 时,发现 java.io 包的相关类真心不少--.看到一堆"排山倒海"般的类,我想,唯有英雄联盟中小炮的台词才能表现此刻我的心情: 跌倒了没?崩 ...

  5. asp.net——地址栏传递中文参数乱码解决方案

    地址栏传递中文参数乱码解决方案: 很多人在使用地址栏传递参数的时候都会遇到一个麻烦的问题(参数为中文时乱码了),那要怎么解决呢? 其实解决这个问题也不怎么难,无非就是给要传递的中文参数一个编码解码的过 ...

  6. MySQL数据库(三)

    1. 创建表 create table student( id int unsigned not null auto_increment primary key, name varchar(8) no ...

  7. vs code开发.net core项目入门

    今天用vs code来开发net core项目,写一下简要的开发流程,主要步骤如下,看完后你会发现特别简单 1.命令如下: (cmd中运行以下命令,下面命令都基于选择好自己的项目路径) 1.新建文件夹 ...

  8. Major OSL changes to catch up

    flat_map optimization for runtime specialization: https://github.com/imageworks/OpenShadingLanguage/ ...

  9. ubuntu里面如何以root身份使用图形界面管理文件?

    nautilus 是gnome的文件管理器,但是如果不是root账号下,权限受限,我们可以通过以下方式以root权限使用! 一,快捷键“Ctrl+Alt+t”,调出shell. 二,在shell中输入 ...

  10. 修复已损坏的交换机IMG

    1.找出与当前交换机匹配的升级镜像以及合适的BOOT启动程序(这要通过渠道获取) 2.搭建 TFTP 服务器. 3.启动交换机,链接console线缆 4.在 BOOT 模式,完成一下操作 1.配置I ...