.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)); } 復制代碼 附上可運行的工程供調試用。原文鏈接開頭已給出,原文中也給出了原文代碼的下載。推薦都下載比對著看,可能會更有幫助。 本篇也是初寫的時候信心滿滿,不知道被各位吐槽後會是怎樣一副情景……之後還應該還會有第二篇,也許是明天,也許是明年……