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