【温故知新】C#基于事件的异步模式(EAP)

在开发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 = 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(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);
        }

运行结果:

调用线程: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、如果不存在UI的话,可以免去AsyncOperation这个步骤,直接在callback通知相应event。

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

4、正因为麻烦,所以.net4.0后面又推出了Task,各种封装,瞬间异步。

实现基于事件的异步模式的最佳做法,见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

时间: 2024-10-05 02:51:00

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

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

什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步方法的取消.进度报告和报告结果.然而.net中并不是所有的类都支持EAP,总结起来有以下17个类支持EAP异步. System.Object的派生类型: System.Activies.WorkflowInvoke System.Deployment.Application.ApplicationD

MSDN搬运 之 [基于事件的异步模式]

基于事件的异步模式概述 那些同时执行多项任务.但仍能响应用户交互的应用程序通常需要实施一种使用多线程的设计方案.System.Threading 命名空间提供了创建高性能多线程应用程序所必需的所有工具,但要想有效地使用这些工具,需要有丰富的使用多线程软件工程的经验.对于相对简单的多线程应用程序,BackgroundWorker 组件提供了一个简单的解决方案.对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类. 基于事件的异步模式具有多线程应用程序的优点,同时隐匿了多线程设计中固有

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

四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: ·                  “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序. ·                  同时执行多个操作,每个操作完成时都会接到通知(在通知中可以区分是完成了哪个操作). ·                  等待资源变得可用,但不会停止(“挂起”)您的应用程序. ·  

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

实现异步处理的方法很多,经常用的有基于委托的方式,今天记录的是基于事件的异步模式.利用BackgroundWorker组件可以很轻松的实现异步处理,并且该组件还支持事件的取消.进度报告等功能.本文以计算两个数X.Y的和为例. 程序界面如下图,其中三个文本框分别为两个加数和处理结果,两个按钮为计算和取消,按钮下方为进度条. 引入BackgroundWorker组件,为DoWork.ProgressChanged.RunWorkerCompleted三个事件指定方法.将WorkerReportsPr

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

一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的,既然存在这样的问题,微软当然也应该提供给我们解决问题的方案了,所以微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型--EAP. 实现了基于事件的异步模式的类将

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

一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的,既然存在这样的问题,微软当然也应该提供给我们解决问题的方案了,所以微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是我这个专题中介绍的基于事件的异步编程模型--EAP. 二.介绍 实现了基于事件的异步

实现基于Task的异步模式

返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编译器会使用TAP执行必要的转换从而异步地实现方法.这样的方法应该返回Task或者Task<TResult>类型.在后者的案例中,方法体应该返回一个TResult,且编译器将确保通过返回的Task<TResult>是可利用的.相似地,方法体内未经处理的异常会被封送到输出的task,造成返

实践基于Task的异步模式

Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueWith的方法实现的.基于语言的异步支持通过允许在正常控制流内部等待异步操作隐藏callbacks,具有和编译器生成的代码相同的API级别的支持. 在.Net 4.5,C#直接异步地支持等待的Task和Task<TResult>,在C#中使用"await"关键字.如果等待一个Ta

C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)

学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式). --用AggregateException处理Task上的未处理异常. --取消任务. CancellationToken --async修饰方法, 返回Task. task.wait(100)可以阻塞现场. a