學習迭代器實現C#異步編程——仿async/await(一)

  .NET 4.5的async/await真是個神奇的東西,巧妙異常以至我不由對其實現充滿好奇,但一直難以窺探其門徑。不意間讀了此篇強文《Asynchronous Programming in C# using Iterators》,猶如醍醐灌頂,茅廁頓開,思路猶如尿崩。美玉不敢獨享,故寫此篇,將所學中一些思考與諸君共享,期拋磚引玉,擦出一些基情火花……編程

  強文《Asynchronous Programming in C# using Iterators》出自大牛,大牛眼界高遠。故文中所述較爲簡略,而文中所附代碼亦較爲晦澀,鄙人駑鈍,反覆閱讀思考數十遍,方品出些味道。故本篇會對原文代碼一個最簡化的提取,再進行分析。asp.net

  強文提到的用迭代器在C#中進行異步編程,最核心的思想就是經過yield return產生一個可IEnumerable<Asyncable>的集合,取出第一個Asyncable,執行Async方法並當即返回,將控制權交給上層調用方,同時Async方法在完成後會回調MoveNext繼續遍歷以前集合。(本篇提到的最底層的Async方法均是以Begin/End來實現,以前的隨筆也說過async/await只是語法糖)異步

  大概畫了個草圖意思一下,花了好久時間也沒能畫得特別清晰明瞭,請見諒,有好的想法還望賜教。async

  接下來咱們根據具體的代碼來分析,首先看一下Main方法。第一行是一個異步方法,咱們期待的結果是第二行的輸出在異步方法結束前執行。ide

        static void Main(string[] args)
        {
            AsyncMethod("http://www.microsoft.com").Execute();

            Console.WriteLine("我先執行,不等你了");

            Console.ReadLine();
        }

  AsyncMethod方法返回了一個IEnumerable<IAsync>的集合,這裏須要注意的是AsyncMethod方法的返回值實際上是一個相似狀態機的類對象,這個對象自己不會執行內部的代碼語句,咱們須要一個Execute方法來開始遍歷運行這個集合裏的代碼語句。而AsyncMethod方法裏的語句又根據yield return的個數來劃分紅塊,每一次MoveNext方法實際上是執行一塊的代碼。也就是說存在多少個yield return,就會有多少次回調。異步編程

  

        static IEnumerable<IAsync> AsyncMethod(string url)
        {
            WebRequest req = HttpWebRequest.Create(url);
            Console.WriteLine("[{0}] starting", url);

            // asynchronously get the response from http server
            Async<WebResponse> response = req.GetResponseAsync();
            yield return response;

            Console.WriteLine("[{0}] got response", url);
            Stream resp = response.Result.GetResponseStream();

            foreach (var item in resp.ReadToEndAsync())
            {
                yield return item;
            }

            Console.WriteLine("done");
        }

  GetResponseAsync方法看上去很簡單,就是封裝了一下Beginxx/Endxxx。爲何說看上去簡單,後面會提到。AsyncPrimitive就是咱們會接觸到最底層的Asyncable對象了,本篇一切異步都是基於它來實現的。this

        public static Async<WebResponse> GetResponseAsync(this WebRequest req)
        {
            return new AsyncPrimitive<WebResponse>(req.BeginGetResponse, req.EndGetResponse);
        }

  ReadToEndAsync和AsyncMethod方法同樣,是創建在可返回Async<T>對象的已有Async方法的基礎上。url

        public static IEnumerable<IAsync> ReadToEndAsync(this Stream stream)
        {
            MemoryStream ms = new MemoryStream();
            int read = -1;
            while (read != 0)
            {
                byte[] buffer = new byte[512];
                Async<int> count = stream.ReadAsync(buffer, 0, 512);
                yield return count;

                Console.WriteLine("[{0}] got data: {1}", "url", count.Result);
                ms.Write(buffer, 0, count.Result);
                read = count.Result;
            }
        }

  ReadAsync一樣是經過Beginxxx/Endxxx來實現異步。他和GetResponseAsync同樣是創建在AsyncPrimitive對象上的Async方法。spa

        public static Async<int> ReadAsync(this Stream stream, byte[] buffer, int offset, int count)
        {
            return new AsyncPrimitive<int>(
                (callback, st) => stream.BeginRead(buffer, offset, count, callback, st),
                stream.EndRead);
        }

  下面讓咱們重點來看一下AsyncPrimitive類,你可能已經發現這個類和Task<T>.Factory.FromAsync方法有點類似。但是若是讓本身來實現,怕不是想象的那麼簡單。.net

    public class AsyncPrimitive<T> : Async<T>
    {
        Action<Action<T>> func;

        public AsyncPrimitive(Func<AsyncCallback, object, IAsyncResult> begin, Func<IAsyncResult, T> end)
        {
            this.func = (cont) => begin(delegate(IAsyncResult res) { cont(end(res)); }, null);
        }

        public override void ExecuteStep(Action cont)
        {
            func((res) =>
            {
                result = res;
                completed = true;
                cont();
            });
        }
    }

  徹底由委託、匿名方法和lambda表達式組成的類。在ExecuteStep被調用前,它不會作任何事情,僅僅是構建了一個Action<Action<T>>的委託。那麼分析這個類纔是本篇最主要的目的,但難點在於這貨不是三言兩語就能說清楚的,鄙人在暈乎了好久好久之後,將該類翻譯以下,去除了全部的匿名方法和lambda表達式。看明白了這個類,就明白了經過迭代器是如何實現異步的。

    public class AsyncPrimitive<T> : Async<T>
    {
        Action<Action<T>> func;

        public AsyncPrimitive(Func<AsyncCallback, object, IAsyncResult> begin, Func<IAsyncResult, T> end)
        {
            this.Begin = begin;
            this.End = end;

            this.func = this.ActionActionT;
        }

        Func<IAsyncResult, T> End { get; set; }
        Func<AsyncCallback, object, IAsyncResult> Begin { get; set; }
        Action<T> RunInCallback { get; set; }
        Action ActionOuter {get;set;}

        private void Callback(IAsyncResult ar)
        {
            this.RunInCallback(this.End(ar));
        }

        private void ActionActionT(Action<T> cont)
        {
            this.RunInCallback = cont;
            this.Begin(this.Callback, null);
        }

        private void ActionT(T res)
        {
            this.result = res;
            this.completed = true;
            this.ActionOuter();
        }

        public override void ExecuteStep(Action cont)
        {
            this.ActionOuter = cont;
            this.func(this.ActionT);
        }
    }

  直觀的就能夠感受到lambda幫助咱們省略了多少代碼,在簡潔的同時,也增長了些許理解的難度。代碼就是最好的註釋,我實在沒信心去用文字描述這個類如何工做。

  最後補充Execute方法,這個方法真正的開始執行Async方法,大致思路就是遍歷集合,但不是經過while循環,而是經過callback來執行下一個MoveNext。

        public static void Execute(this IEnumerable<IAsync> async)
        {
            AsyncExtensions.Run(async.GetEnumerator());
        }

        internal static void Run(IEnumerator<IAsync> en)
        {
            if (!en.MoveNext()) return;
            en.Current.ExecuteStep
                (() => AsyncExtensions.Run(en));
        }

  附上可運行的工程供調試用。原文連接開頭已給出,原文中也給出了原文代碼的下載。推薦都下載比對着看,可能會更有幫助。

  本篇也是初寫的時候信心滿滿,不知道被各位吐槽後會是怎樣一副情景……以後還應該還會有第二篇,也許是明天,也許是明年……

代碼下載

相關文章
相關標籤/搜索