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

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

1 使用異步編程的緣由

同步編程,服務器在響A服務的數據庫讀取,網頁請求或者文件請求(這裏咱們統稱爲IO操做),若是延遲很大,此時若是來了B服務的IO請求,可能沒法及時響應(阻塞),此時異步編程模式(非阻塞)應運而生。html

異步編程模式是爲了不性能瓶頸並加強你的應用程序的整體響應能力。git

2 異步編程模式

2.1 APM模式

APM(Asynchronous Programming Model) 是 net 1.0時期就提出的一種異步模式,而且基於IAsyncResult接口實現BeginXXX和EndXXX相似的方法.github

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等的點擊事件,網絡庫的接收方法中採用事件驅動型的異步編程模式。async

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
相關文章
相關標籤/搜索