C# 異步編程1 APM 異步程序開發

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

我是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模型如何實現異步編程。async

IAsyncResult接口異步編程

IAsyncResult接口定義了異步功能的狀態,該接口具體屬性及含義以下:post

   //     表示異步操做的狀態。
    [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;
        }
    }

示例代碼分析:this

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

在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 很是簡單,Num一、Num2兩個屬性爲要計算的數值。BeginWork 建立WorkerAsyncResult對象、並將要計算的兩個數值Num一、Num二、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,謝謝。

相關文章
相關標籤/搜索