海量連線
小明同學很早就來到了教室,用過PLINQ之後,果然回不去了。從他的眼神中,我可以看得出來,小明對我今天的分享很是期待。
今天我們聊一個很酷炫,但是對於有多執行緒程式設計經驗的人卻不太好理解的程式設計模型:async 和 await。我們在介紹Task的那一篇文章中,就提到Task結合async和await可以非常優雅地寫程式。今天我們就來一探究竟吧!
例項演示我們模擬一個需要非同步執行的工作流,其中有3個主要步驟,且下一個步驟,需要依賴上一個步驟的結果:
static Task<int> Func1(int seed){ return Task.Run(() => { Console.WriteLine($"seed: {seed}\t Func1 is Running... "); Task.Delay(2000).Wait(); return 1 + seed; });}static Task<int> Func2(int p1){ return Task.Run(() => { Console.WriteLine($"p1:{p1}\t\t Func2 is Running... "); Task.Delay(2000).Wait(); return 2 + p1; });}static Task<int> Func3(int p2){ return Task.Run(() => { Console.WriteLine($"p2:{p2}\t\t Func3 is Running... "); Task.Delay(2000).Wait(); return 3 + p2; });}
然後我們用常規Task來實現這個工作流的呼叫過程:
static void WorkFlow(){ var t1 = Func1(100); var w1 = t1.GetAwaiter(); w1.OnCompleted(() => { var t2 = Func2(w1.GetResult()); var w2 = t2.GetAwaiter(); w2.OnCompleted(() => { var t3 = Func3(w2.GetResult()); t3.Wait(); Console.WriteLine($"Result:{t3.Result}"); }); });}static void Main(string[] args){ Console.WriteLine("Hello async/await World!"); WorkFlow(); Console.ReadKey();}
執行結果如下:
執行結果
從程式來看,沒有太特殊的東西。但是有一點,如果這個工作流的步驟增多,我們的WorkFlow這個函式,不太優雅,其中的OnCompleted會越來越深。當然我們可以使用Task的Wait函式,來解決這個問題,但是這樣的話WorkFlow這個子函式內部,就會阻塞了:
static void WorkFlow(){ var t1 = Func1(100); t1.Wait(); var t2 = Func2(t1.Result); t2.Wait(); var t3 = Func3(t2.Result); t3.Wait(); Console.WriteLine($"Result:{t3.Result}");}
async/await
有沒有保持WorkFlow不阻塞的方法呢?
有,就是利用async和await來改寫:
static async void WorkFlow(){ var r1 = await Func1(100); var r2 = await Func2(r1); var r3 = await Func3(r2); Console.WriteLine($"Result:{r3}");}static void Main(string[] args){ Console.WriteLine("Hello async/await World!"); WorkFlow(); Console.ReadKey();}
程式的結果,沒有任何改變。但是寫法簡化了非常多。幾乎和我們寫序列程式一樣了。如果有同學不理解這裡一定為什麼一定要將WorkFlow寫成非阻塞模式的,沒關係,在將來某一天,你很可能會遇到這樣的場景的。
await 就是 非同步等待的意思,await只能用於async修飾的函式內部。async修飾的函式,可以像常規函式一樣呼叫。但是在遇到async函式內部的await關鍵字之後,相當於該函式就先返回了。待await等待的操作完成之後,又會回到函式中執行await之後的程式碼。是不是很繞。。。
此處應該有流程圖
程式執行流程也比較奇怪,很可能和你預想的執行順序不同。為了研究程式執行的流程,我加入一些標記性的程式碼,再來檢視執行結果:
static async void WorkFlow(){ var r1 = await Func1(100); Console.WriteLine("Func1 end"); var r2 = await Func2(r1); Console.WriteLine("Func2 end"); var r3 = await Func3(r2); Console.WriteLine("Func3 end"); Console.WriteLine($"Result:{r3}");}static void Main(string[] args){ Console.WriteLine("Hello async/await World!"); WorkFlow(); Console.WriteLine("WorkFlow end"); Console.ReadKey();}
結果如下:
執行流程
我們注意一點,WorkFlow這個函式,在Fun1執行結束之前(第一個await後),就已經返回了,其後的程式碼就開始執行了。這也是為什麼要在最後面加上【Console.ReadKey();】的原因,這是為了阻止程式即出。
感興趣的同學,一定要動手單步執行這個程式,研究函式執行的流程。
這個過程很難描述清楚,一定要親自體驗,才能發現其奧妙之處。
要是覺得這個流程奇怪的同學,可以去了解一下目前很火的一個概念:協程。雖然我沒的查到官方的資料表明async/await就是C#中的協程,但是其用法和功能,都和協程非常像了。這其實是一種全新的程式設計正規化,在處理海量使用者併發的時候,經常使用。瞭解協程之後,理解這種流程就會容易很多。
佈置作業透過除錯以上的示例程式,瞭解async/await的執行流程。也可以自行修改程式,模擬不同的場景。
查詢協程相關資料,輔助理解async/await
小明同學
往期回顧下面是給同學們準備的乾貨: