程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 學習迭代器實現C#異步編程——仿async/await(一)

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

編輯:C#入門知識

 .NET 4.5的async/await真是個神奇的東西,巧妙異常以致我不禁對其實現充滿好奇,但一直難以窺探其門徑。不意間讀了此篇強文《Asynchronous Programming in C# using Iterators》,猶如醍醐灌頂,茅廁頓開,思路猶如尿崩。美玉不敢獨享,故寫此篇,將所學中一些思考與諸君共享,期拋磚引玉,擦出一些基情火花……     強文《Asynchronous Programming in C# using Iterators》出自大牛,大牛眼界高遠。故文中所述較為簡略,而文中所附代碼亦較為晦澀,鄙人驽鈍,反復閱讀思考數十遍,方品出些味道。故本篇會對原文代碼一個最簡化的提取,再進行分析。     強文提到的用迭代器在C#中進行異步編程,最核心的思想就是通過yield return產生一個可IEnumerable<Asyncable>的集合,取出第一個Asyncable,執行Async方法並立即返回,將控制權交給上層調用方,同時Async方法在完成後會回調MoveNext繼續遍歷之前集合。(本篇提到的最底層的Async方法均是以Begin/End來實現,之前的隨筆也說過async/await只是語法糖)     大概畫了個草圖意思一下,花了很久時間也沒能畫得特別清晰明了,請見諒,有好的想法還望賜教。         接下來我們根據具體的代碼來分析,首先看一下Main方法。第一行是一個異步方法,我們期待的結果是第二行的輸出在異步方法結束前執行。   復制代碼         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對象了,本篇一切異步都是基於它來實現的。           public static Async<WebResponse> GetResponseAsync(this WebRequest req)         {             return new AsyncPrimitive<WebResponse>(req.BeginGetResponse, req.EndGetResponse);         }   ReadToEndAsync和AsyncMethod方法一樣,是建立在可返回Async<T>對象的已有Async方法的基礎上。   復制代碼         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方法。   復制代碼         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方法有點相似。可是如果讓自己來實現,怕不是想象的那麼簡單。   復制代碼     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));         } 復制代碼   附上可運行的工程供調試用。原文鏈接開頭已給出,原文中也給出了原文代碼的下載。推薦都下載比對著看,可能會更有幫助。     本篇也是初寫的時候信心滿滿,不知道被各位吐槽後會是怎樣一副情景……之後還應該還會有第二篇,也許是明天,也許是明年……

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved