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

JerryMouseLi發表於2020-12-07

深入理解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

相關文章