C# 非同步編程1 APM 非同步程式開發

来源:http://www.cnblogs.com/dw039/archive/2017/09/08/7490934.html
-Advertisement-
Play Games

APM非同步編程模式最具代表性的特點是:一個非同步功能由以Begin開頭、End開頭的兩個方法組成。Begin開頭的方法表示啟動非同步功能的執行,End開頭的方法表示等待非同步功能執行結束並返回執行結果。 ...


C#已有10多年曆史,單從微軟2年一版的更新進度來看活力異常旺盛,C#中的非同步編程也經歷了多個版本的演化,從今天起著手寫一個系列博文,記錄一下C#中的非同步編程的發展歷程。廣告一下:喜歡我文章的朋友,請點下麵的“關註我”。謝謝

我是2004年接觸並使用C#的,那時C#版本為1.1,所以我們就從就那個時候談起。那時在大學里自己看書寫程式,所寫的程式大都是同步程式,最多啟動個線程........其實在C#1.1的時代已有完整的非同步編程解決方案,那就是APM(非同步編程模型)。如果還有不瞭解“同步程式、非同步程式”的請自行百度哦。

APM非同步編程模型最具代表性的特點是:一個非同步功能由以Begin開頭、End開頭的兩個方法組成。Begin開頭的方法表示啟動非同步功能的執行,End開頭的方法表示等待非同步功能執行結束並返回執行結果。下麵是一個模擬的實現方式(後面將編寫標準的APM模型非同步實現):

public class Worker
    {
        public int A { get; set; }
        public int B { get; set; }
        private int R { get; set; }
        ManualResetEvent et;
        public void BeginWork(Action action)
        {
            et = new ManualResetEvent(false);
            new Thread(() =>
            {
                R = A + B;
                Thread.Sleep(1000);
                et.Set();
                if(null != action)
                {
                    action();
                }
            }).Start();
        }

        public int EndWork()
        {
            if(null == et)
            {
                throw new Exception("調用EndWork前,需要先調用BeginWork");
            }
            else
            {
                et.WaitOne();
                return R;
            }

        } 
    }
        static void Main(string[] args)
        {
           Worker w = new Worker();
            w.BeginWork(()=> {
                Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId,
                    w.EndWork());
            });
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
            Console.ReadLine();
        }

在上面的模擬APM模型中我們使用了 Thread、ManualResetEvent,如果你對多線程和ManualResetEvent不熟悉,請閱讀我之前的Blog《C#多線程的用法系列-線程間的協作ManualResetEvent》。C#中使用非同步編程不可避免的會涉及到多線程的知識,雖然微軟在Framework中做了很多封裝,但朋友們應該掌握其本質。

上面模擬的APM非同步模型之所以簡單,是因為C#發展過程中引入了很多優秀的語法規則。上例我們較多的使用了Lambda表達式,如果你不熟悉 匿名委托與lambda表達式可看我之前的Bolg《匿名委托與Lambda表達式》。上面做瞭如此多的廣告,下麵我們來看一下標準的APM模型如何實現非同步編程。

IAsyncResult介面

IAsyncResult介面定義了非同步功能的狀態,該介面具體屬性及含義如下:

   //     表示非同步操作的狀態。
    [ComVisible(true)]
    public interface IAsyncResult
    {
        //
        // 摘要:
        //     獲取一個值,該值指示非同步操作是否已完成。
        //
        // 返回結果:
        //     如果操作已完成,則為 true;否則為 false。
        bool IsCompleted { get; }
        //
        // 摘要:
        //     獲取用於等待非同步操作完成的 System.Threading.WaitHandle。
        //
        // 返回結果:
        //     用於等待非同步操作完成的 System.Threading.WaitHandle。
        WaitHandle AsyncWaitHandle { get; }
        //
        // 摘要:
        //     獲取一個用戶定義的對象,該對象限定或包含有關非同步操作的信息。
        //
        // 返回結果:
        //     一個用戶定義的對象,限定或包含有關非同步操作的信息。
        object AsyncState { get; }
        //
        // 摘要:
        //     獲取一個值,該值指示非同步操作是否同步完成。
        //
        // 返回結果:
        //     如果非同步操作同步完成,則為 true;否則為 false。
        bool CompletedSynchronously { get; }
    }
註意:模型示例1中的 ManualResetEvent 繼承自 WaitHandle
APM傳說實現方式
在瞭解了IAsyncResult介面後,我們來通過實現 IAsyncResult 介面的方式完成對模擬示例的改寫工作,代碼如下:
    public class NewWorker
    {
        public class WorkerAsyncResult : IAsyncResult
        {
            AsyncCallback callback;
            public WorkerAsyncResult(int a,int b, AsyncCallback callback, object asyncState) {
                A = a;
                B = b;
                state = asyncState;
                this.callback = callback;
                new Thread(Count).Start(this);
            }
            public int A { get; set; }
            public int B { get; set; }

            public int R { get; private set; }

            private object state;
            public object AsyncState
            {
                get
                {
                    return state;
                }
            }
            private ManualResetEvent waitHandle;
            public WaitHandle AsyncWaitHandle
            {
                get
                {
                    if (null == waitHandle)
                    {
                        waitHandle = new ManualResetEvent(false);
                    }
                    return waitHandle;
                }
            }
            private bool completedSynchronously;
            public bool CompletedSynchronously
            {
                get
                {
                    return completedSynchronously;
                }
            }
            private bool isCompleted;
            public bool IsCompleted
            {
                get
                {
                    return isCompleted;
                }
            }
            private static void Count(object state)
            {
                var result = state as WorkerAsyncResult;
                result.R = result.A + result.B;
                Thread.Sleep(1000);
                result.completedSynchronously = false;
                result.isCompleted = true;
                ((ManualResetEvent)result.AsyncWaitHandle).Set();
                if (result.callback != null)
                {
                    result.callback(result);
                }
            }
        }
        public int Num1 { get; set; }
        public int Num2 { get; set; }

        public IAsyncResult BeginWork(AsyncCallback userCallback, object asyncState)
        {
            IAsyncResult result = new WorkerAsyncResult(Num1,Num2,userCallback, asyncState);
            return result;
        }

        public int EndWork(IAsyncResult result)
        {
            WorkerAsyncResult r = result as WorkerAsyncResult;
            r.AsyncWaitHandle.WaitOne();
            return r.R;
        }
    }

示例代碼分析:

上面代碼中NewWorker的內部類 WorkerAsyncResult 是關鍵點,它實現了 IAsyncResult 介面並由它來負責開啟新線程完成計算工作。

在WorkerAsyncResult中增加了 A、B兩個私有屬性來存儲用於計算的數值,一個對外可讀不可寫的屬性R,用於存儲WorkerAsyncResult內部運算的結果。AsyncWaitHandle屬性由 ManualResetEvent 來充當,併在首次訪問時創建ManualResetEvent(但不釋放)。其他介面屬性正常實現,沒有什麼可說。

WorkerAsyncResult 中新增 static Count 方法,參數 state 為調用Count方法的當前WorkerAsyncResult對象。Count 方法在 WorkerAsyncResult 對象的新啟線程中運行,因此Thread.Sleep(1000)將阻塞新線程1秒中。然後設置當前WorkerAsyncResult對象是否同步完成為false,非同步完成狀態為true,釋放ManualResetEvent通知以便等待線程獲取通知進入執行狀態,判斷是否有非同步執行結束回調委托,存在則回調之。

NewWorker 非常簡單,Num1、Num2兩個屬性為要計算的數值。BeginWork 創建WorkerAsyncResult對象、並將要計算的兩個數值Num1、Num2、userCallback回調委托、object 類型的 asyncState 傳入要創建的WorkerAsyncResult對象。經過此步操作,WorkerAsyncResult對象獲取了運算所需的所有數據、運算完成後的回調,並馬上啟動新線程進行運算(執行WorkerAsyncResult.Count方法)。

因為WorkerAsyncResult.Count執行在新線程中,在該線程外部無法準確獲知新線程的狀態。為了滿足外部線程與新線程同步的需求,在NewWorker中增加EndWork方法,參數類型為IAsyncResult。要調用EndWork方法應傳入BeginWork 獲取的WorkerAsyncResult對象,EndWork方法獲取WorkerAsyncResult對象後,調用WorkerAsyncResult.AsyncWaitHandle.WaitOne()方法,等待獲取ManualResetEvent通知,在獲取到通知時運算線程已運算結束(線程並未結束),下一步獲取運算結果R並返回。

接下來是NewWorker調用程式,如下:

        static void Main(string[] args)
        {
            NewWorker w2 = new NewWorker();
            w2.Num1 = 10;
            w2.Num2 = 12;
            IAsyncResult r = null;
            r = w2.BeginWork((obj) => {
            Console.WriteLine("Thread Id:{0},Count:{1}",Thread.CurrentThread.ManagedThreadId,
            w2.EndWork(r));
            }, null);
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);
            Console.ReadLine();
        }

下圖我簡單畫的程式調用過程,有助於各位朋友理解:

標準的APM模型非同步編程,對應開發人員來說過於複雜。因此通過實現 IAsyncResult 介面進行非同步編程,就是傳說中的中看不中用(罪過罪過.....)。

Delegate非同步編程(APM 標準實現)

C#中委托天生支持非同步調用(APM模型),任何委托對象後"."就會發現BeginInvoke、EndInvoke、Invoke三個方法。BeginInvoke為非同步方式調用委托、EndInvoke等待委托的非同步調用結束、Invoke同步方式調用委托。因此上面的標準APM實例,可藉助  delegate 進行如下簡化。

上面NewWorker使用委托方式改寫如下:

    public class NewWorker2
    {
        Func<int, int, int> action;
        public NewWorker2()
        {
            action = new Func<int, int, int>(Work);
        }
        public IAsyncResult BeginWork(AsyncCallback callback, object state)
        {
            dynamic obj = state;
            return action.BeginInvoke(obj.A, obj.B, callback, this);
        }

        public int EndWork(IAsyncResult asyncResult)
        {
            try
            {
                return action.EndInvoke(asyncResult);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private int Work(int a, int b)
        {
            Thread.Sleep(1000);
            return a + b;
        }
    }

調用程式:

        static void Main(string[] args)
        {
            NewWorker2 w2 = new NewWorker2();
            IAsyncResult r = null;
            r = w2.BeginWork((obj) =>
            {
                Console.WriteLine("Thread Id:{0},Count:{1}", Thread.CurrentThread.ManagedThreadId,
                    w2.EndWork(r));
            }, new { A = 10, B = 11 });
            Console.WriteLine("Thread Id:{0}", Thread.CurrentThread.ManagedThreadId);

            Console.ReadLine();
        }

上面的使用委托進行APM非同步編程,比實現 IAsyncResult 介面的方式精簡太多、更易理解使用。因此這裡建議朋友們 delegate 非同步調用模型應該掌握起來,而通過實現 IAsyncResult 介面的傳說方式看你的喜好吧。

同時上面的delegate中我們還用了一些委托、匿名對象、動態類型等知識,如果你感興趣的話都可從我的Blog中找到相關知識供你參考。

最後再廣告一次:喜歡我文章的朋友請關註一下我的blog,謝謝。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 但是這種操作方式存在兩個問題:1.通過反射的方式,效率不高。2.如果是一個感測器,那麼定義的實時數據屬性不多;如果是一個站點(可以理解為生產單位或網關層)上傳的數據,可能有成千上萬監測點,那麼不可能在繼承DeviceDynamic介面的子類中定義這麼多屬性。 ...
  • 本篇作為技術分享系列的第一篇,詳細講一下 SVG 的解析和繪製,這部分功能的研究和最終實現由團隊的 @黃超超 同學負責,感謝提供技術文檔和支持。 首先我們來看一下 SVG 的文件結構和組成 SVG (Scalable Vector Graphics) 是一種可縮放矢量圖形,使用 XML 格式來定義, ...
  • 開篇先來說一下寫這篇文章的初衷。 初到來畫,通讀了來畫 UWP App 的代碼,發現裡面確實有很多比較高深的技術點,同時也是有很多問題的,擴展性,耦合,性能,功能等等。於是我們決定從頭重構這個產品,做一個全新的 “來畫Pro” 出來,歷經三個月的世間,這個產品終於正式上架。 (做個小廣告,在 Win ...
  • 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 8 namespace Wrox.Pro... ...
  • 上個月在網上看到一個用web實現簡單AR效果的文章,然後自己一路折騰,最後折騰出來一個 Asp.net+WebSocket+Emgucv實時人臉識別的東西,網上也有不少相關資料,有用winform的也有asp.net的。其實人臉識別技術早就成熟了,就是沒機會接觸這方面。百度了一下 找到好多,Jque ...
  • 本文主要介紹下運用docker虛擬技術打包Asp.net core應用。 Docker作為一個開源的應用容器引擎,近幾年得到廣泛的應用,使用Docker我們可以輕鬆實現應用的持續集成部署,一次打包,到處運行。 ...
  • step one:(找入口) using System.Reflection; //引用需要用到的命名空間 做任何事都要有開始的地方,不例外,反射也要先找到反射的入口,舉個慄子: Assembly assemble = Assembly.Load("SqlServer"); //反射的入口::動態的 ...
  • WebForm.aspx 頁面通過 AJAX 訪問WebForm.aspx.cs類中的方法,獲取數據 WebForm1.aspx 頁面 (原生AJAX請求,寫法一) WebForm1.aspx 頁面 (AJAX請求,寫法二,一般情況下我們用這種) WebForm1.aspx.cs 如果你是try.. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...