博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【温故知新】C#基于事件的异步模式(EAP)
阅读量:6968 次
发布时间:2019-06-27

本文共 7646 字,大约阅读时间需要 25 分钟。

在开发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 模式呢?微软给出了很好的答案,见

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

代码场景

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

核心代码如下:

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;        ///         /// 构造函数        ///         public Downloader()        {            onProgressChangedDelegate = new SendOrPostCallback(onProgressChanged);            onDownloadCompletedDelegate = new SendOrPostCallback(onDownloadComplete);        }        ///         /// 通过AsyncOperation调用onProgressChangedDelegate委托关联该函数,保证运行在合适线程        ///         ///         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);            }        }        ///         /// 异步下载文件        ///         ///         ///         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));        }        ///         /// 提供给外部调用的同步方法        ///         ///         ///         /// 
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 = 0; i < 10; i++) { int p = i * 10; Debug.WriteLine("执行线程:" + Thread.CurrentThread.ManagedThreadId + ",传输进度:" + p + "%"); Thread.Sleep(1000); //不为空则是异步 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
.Run(() => { return DownLoad(url, name, asyncOp); }); if (DownloadCompleted != null) { DownloadCompletedEventArgs e = new DownloadCompletedEventArgs(r, null, false, null); DownloadCompleted(this, e); } }

调用方式不变。

 

实现基于事件的异步模式的最佳做法,见

参考:

 

补充:

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

转载于:https://www.cnblogs.com/leestar54/p/4591792.html

你可能感兴趣的文章
记录搭建Ionic开发环境,创建Ionic工程遇到的坑(2016年12月09日更新)
查看>>
元数据驱动设计——连接设计与开发的敏捷桥梁
查看>>
将敏捷应用于工业机械开发
查看>>
百度发布智能电视伴侣,并公布短视频计划
查看>>
Java将每半年发布一个版本
查看>>
Kubernetes日志分析利器:Elassandra部署使用指南
查看>>
阿里巴巴直播防控中的实人认证技术
查看>>
GitHub启用安全告警功能
查看>>
软件测试工程师的核心竞争力是什么?
查看>>
Uber开源其大规模指标平台M3
查看>>
安卓开源项目周报0110
查看>>
物联网技术周报第 89 期: Intel Curie 与模式匹配进行可穿戴服装开发
查看>>
管理者在敏捷中的角色
查看>>
网易数据基础平台建设经验谈
查看>>
Oracle Cloud Native Framework推出云原生解决方案
查看>>
到底谁应该对软件开发的质量负责?
查看>>
JavaScript高级程序设计-摘要笔记-6
查看>>
React服务端渲染Next.js 8发布,新增无服务器功能
查看>>
Facebook何恺明团队提出SlowFast网络,视频识别无需预训练
查看>>
linux基础命令介绍七:网络传输与安全
查看>>