深入理解C#中的异步(一)——APM模式EAP模式

深入理解C#中的异步(一)——APM模式EAP模式

1 使用异步编程的原因

同步编程,服务器在响A服务的数据库读取,网页请求或者文件请求(这里我们统称为IO操作),如果延迟很大,此时如果来了B服务的IO请求,可能无法及时响应(阻塞),此时异步编程模式(非阻塞)应运而生。

异步编程模式是为了避免性能瓶颈并增强你的应用程序的总体响应能力。

2 异步编程模式

2.1 APM模式

APM(Asynchronous Programming Model) 是 net 1.0时期就提出的一种异步模式,并且基于IAsyncResult接口实现BeginXXX和EndXXX类似的方法.

2.1.1 APM模式示例代码

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
            WebResponseHandler handler = new WebResponseHandler(WebContentLength.GetResult);
            //IAsyncResult: 异步操作接口(interface)
            //BeginInvoke: 委托(delegate)的一个异步方法的开始
            IAsyncResult result = handler.BeginInvoke( null, null);
            Console.WriteLine("继续做别的事情。");
            //异步操作返回
            Console.WriteLine(handler.EndInvoke(result));
            Console.ReadKey();
        }
    }
    public delegate string WebResponseHandler();
    public class WebContentLength
    {
        public static string GetResult()
        {
            var client = new WebClient();
            var content =  client.DownloadString(new Uri("http://cnblogs.com"));
            return "网页字数统计:"+content.Length;
        }
    }

2.1.2 执行结果

备注:APM又是建立在委托之上的。Net Core中的委托 不支持异步调用,也就是 BeginInvoke 和 EndInvoke 方法,即现代异步编程模型中,官方不推荐此模型。此例子使用 .Net FrameWork4.7框架。

2.1.3 APM回调例子

当异步请求响应完成之后,会自动去调用回调方法,将网页字数统计结果打印。

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
            WebResponseHandler handler = new WebResponseHandler(WebContentLength.GetResult);
            //异步操作接口(注意BeginInvoke方法的不同!)
            IAsyncResult result = handler.BeginInvoke( new AsyncCallback(CalllBack), "AsycState:OK");
            Console.WriteLine("继续做别的事情。");
            Console.ReadKey();
        }
        static void CalllBack(IAsyncResult result)
        {
            WebResponseHandler handler = (WebResponseHandler)((AsyncResult)result).AsyncDelegate;
            Console.WriteLine(handler.EndInvoke(result));
            Console.WriteLine(result.AsyncState);
        }
    }
    public delegate string WebResponseHandler();
    public class WebContentLength
    {
        public static string GetResult()
        {
            var client = new WebClient();
            var content = client.DownloadString(new Uri("http://cnblogs.com"));
            return "网页字数统计:" + content.Length;
        }
    }

备注:可以看出此种回调方式与人的思维逻辑相违背,当在回调函数中存在二级三级回调时,代码可读性变差,编程会变得比平常要困难一些。

2.1.4 执行结果

2.2 EAP模式

EAP(Event-based Asynchronous Pattern)基于事件的异步模式是 .net 2.0提出的,EAP异步编程算是C#对APM的一种补充,让异步编程拥有了一系列状态事件。实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步方法的取消、进度报告和报告结果。然而.net中并不是所有的类都支持EAP。

当我们使用EAP模式进行异步编程时,需要满足以下2个条件:

  1. 要进行异步的方法其方法名应该以XXXAsync结尾
  2. 要有一个名为XXXCompleted的事件监听异步方法的完成
  3. 可增加一个CancelAsync方法用于取消正在执行的异步方法(可选)

备注:当调用基于事件的EAP模式的类的XXXAsync方法时,就开始了一个异步操作,并且基于事件的EAP模式是基于APM模式之上的。EAP 是在 .NET Framework 2.0 版中引入的,在 winform,silverlight或者wpf变成中经常用到。

2.2.1 EAP模式编程示例1

    class Program
    {
        static void Main(string[] args)
        {
            WebClient wc = new WebClient();
            wc.DownloadStringCompleted += Wc_DownloadStringCompleted;
            wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
            Console.WriteLine("执行其他任务。");
            Console.ReadKey();
        }
        private static void Wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            Console.WriteLine("网页字数统计:" + e.Result.Length);
        }
    }

2.2.2 执行结果

总结:此示例代码的编程模式有没有种似曾相识的感觉。没错,winform,wpf等的点击事件,网络库的接收方法中采用事件驱动型的异步编程模式。

2.2.3 封装一个EAP例子

示例代码如下:
Work类,如下代码使用了了事件驱动型异步编程模式,并且对APM模式进行了封装。

    /// <summary>
    /// EAP是对APM的封装
    /// </summary>
    public class Worker
    {
        public enum WorkerStatus
        {
            Cancel = 0, Running = 1, Completed = 2
        }
        public class WorkerEventArgs : EventArgs
        {
            public WorkerStatus Status { get; set; }
            public string Message { get; set; }
        }
        public Worker()
        {
        }
        public event EventHandler<WorkerEventArgs> OnWorkCompleted;
        IAsyncResult asyncResult = null;
        Thread thread = null;
        public void WorkAsync()
        {
            Worker _this = this;

            Action action = () =>
            {
                thread = Thread.CurrentThread;
                Thread.Sleep(1000);
                Console.WriteLine(string.Format("线程:{0},Work Over.", Thread.CurrentThread.ManagedThreadId));

            };
            //result是IAsyncResult对象,此处无用
            //当action委托完成调用之后,会调用如下回调方法。
            asyncResult = action.BeginInvoke((result) =>
            {       
                 WorkerEventArgs e = null;
                try
                {
                    action.EndInvoke(result);
                }
                catch (ThreadAbortException ex)
                {
                    e = new WorkerEventArgs() { Status = WorkerStatus.Cancel, Message = "异步操作被取消" };
                }
                if (null != _this.OnWorkCompleted)
                {
                    _this.OnWorkCompleted.Invoke(this, e);
                }
            },this);
        }
        public void CancelAsync()
        {
            if (null != thread)
                thread.Abort();
        }
    }

winform调用例子

异步嗲用WorkAsync,完成之后,事件异步调用WorkOver方法,并传入EventArgs参数。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        Worker worker;
        private void btnStart_Click(object sender, EventArgs e)
        {
            worker = new Worker();
            worker.OnWorkCompleted += WorkOver;
            worker.WorkAsync();
            Console.WriteLine(string.Format("线程:{0}", Thread.CurrentThread.ManagedThreadId));
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            worker.CancelAsync();
        }
        private void WorkOver(object sender, Worker.WorkerEventArgs e)
        {
            if (null != e)
            {
                if (Worker.WorkerStatus.Cancel == e.Status)
                {
                    MessageBox.Show(e.Message);
                }
            }
            else
            {
                Console.WriteLine(string.Format("线程:{0},委托回调完成.", Thread.CurrentThread.ManagedThreadId));
            }
        }
    }

2.2.4 执行结果

  • 执行完成

  • 未执行完成提前取消

注意事项(重要):

  1. APM异步编程时,因异步代码执行在单独的线程中,异步代码中出现的异常应该在调用EndXXX时捕获。
  2. EAP异步编程时,因上述同样原因,代码中的异常信息会被传递到Completed事件的EventArgs参数中。

3 代码仓库

本文中的代码

4 下篇

预告:
深入理解C#中的异步(二)——TAP模式(基于Async,Await,Task的异步)


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.cnblogs.com/JerryMouseLi/p/14100496.html
posted @ 2020-12-07 23:55  JerryMouseLi  阅读(1876)  评论(2编辑  收藏  举报