上篇對[foreach]的淺究到發現[yield]寫完后,覺得對[yield]還沒有理解清楚,想起曾經看過一位大牛的帖子講的很深刻(鏈接在此),回顧了下,在這里寫出自己的理解,與各位分享。
一、通常的異步
現在我們假設一種平時經常遇到的情況,現有三個方法,其中funcOne和funcTwo比較耗時需要異步執行,而且他們的邏輯是必須在funcOne執行完后才可以執行funcTwo,同理funcTwo執行完后才能執行funcThree。
按照這樣的設定,通常的做法請看代碼段[1]:

1 public class Program 2 { 3 public delegate void CallBack(string nextName); 4 public void funcOne(CallBack callback) 5 { 6 ThreadPool.QueueUserWorkItem((state1) => 7 { 8 Console.WriteLine("[One] async Continue!"); 9 Console.WriteLine("[One] do something!"); 10 callback("Called Two"); 11 }); 12 } 13 public void funcTwo(CallBack callback) 14 { 15 ThreadPool.QueueUserWorkItem((state2) => 16 { 17 Console.WriteLine("[Two] async Continue!"); 18 Console.WriteLine("[Two] do something!"); 19 callback("Called Three"); 20 }); 21 } 22 public void funcThree(CallBack callback) 23 { 24 Console.WriteLine("[Three] do something!"); 25 callback("Called ..."); 26 } 27 static void Main() 28 { 29 Program p = new Program(); 30 p.funcOne((name1) => 31 { 32 Console.WriteLine(name1); 33 p.funcTwo((name2) => 34 { 35 Console.WriteLine(name2); 36 //執行funcThree 37 p.funcThree((name3) => 38 { 39 Console.WriteLine(name3); 40 //當然還有可能繼續嵌套 41 Console.WriteLine("End!"); 42 }); 43 }); 44 }); 45 Console.Read(); 46 } 47 }
相信看完代碼后我們的感覺是一樣的,好繁瑣,就是不斷的嵌套!那有沒有方法可以避免這樣呢,也就是說用同步的寫法來寫異步程序。
二、改進后的異步
該[yield]粉墨登場了,先看代碼段[2]:

1 //三個方法以及委托CallBack的定義不變,此處不再列出。 2 //新增了靜態的全局變量enumerator,和靜態方法funcList. 3 public static System.Collections.IEnumerator enumerator = funcList(); 4 public static System.Collections.IEnumerator funcList() 5 { 6 Program p = new Program(); 7 p.funcOne((name1) => 8 { 9 enumerator.MoveNext(); 10 }); 11 yield return 1; 12 Console.WriteLine("Called Two"); 13 p.funcTwo((name2) => 14 { 15 enumerator.MoveNext(); 16 }); 17 yield return 2; 18 Console.WriteLine("Called Three"); 19 p.funcThree((name3) => 20 { 21 //當然還有可能繼續嵌套 22 }); 23 Console.WriteLine("Called ..."); 24 Console.WriteLine("End!"); 25 yield return 3; 26 } 27 28 //變化后的Main函數 29 static void Main() 30 { 31 enumerator.MoveNext(); 32 Console.Read(); 33 }
現在看看,是不是清爽了一些,沒有無止盡的嵌套。代碼中我們只需要定義一個迭代器,在迭代器中調用需要同步執行的方法,用[yield]來分隔,各方法通過在callback里調用迭代器的MoveNext()方法來保持同步。
通過這樣的實踐,我們可以理解為每當[yield return ..],程序會把迭代器的[上下文環境]暫時保存下來,等到MoveNext()時,再調出來繼續執行到下一個[yield return ..]。
三、是我想太多
以上純屬瞎扯,在.net 4.5之后,我們有了神器:async/await,下面看看多么簡潔吧。
代碼段[3]:

1 public class Program 2 { 3 public async Task funcOne() 4 { 5 Console.WriteLine("[One] async Continue!"); 6 Console.WriteLine("[One] do something!"); 7 //await ... 8 } 9 public async Task funcTwo() 10 { 11 Console.WriteLine("[Two] async Continue!"); 12 Console.WriteLine("[Two] do something!"); 13 //await ... 14 } 15 public void funcThree() 16 { 17 Console.WriteLine("[Three] do something!"); 18 } 19 public static async Task funcList() 20 { 21 Program p = new Program(); 22 await p.funcOne(); 23 await p.funcTwo(); 24 p.funcThree(); 25 //無盡的嵌套可以繼續await下去。 26 Console.WriteLine("End!"); 27 } 28 static void Main() 29 { 30 funcList(); 31 Console.Read(); 32 } 33 }
我已經感覺到了您的驚嘆之情。
async修飾符將方法指定為異步方法(注意!:異步不異步,並不是async說了算,如果這個方法中沒有await語句,就算用了async修飾符,它依然是同步執行,因為它就沒有異步基因)。
被指定為異步方法后,方法的返回值只能為Task、Task<TResult>或者void,返回的Task對象用來表示這個異步方法,你可以對這個Task對象進行控制來操作這個異步方法,詳細的可以參考msdn中給出的Task解釋與示例代碼。
await用於掛起主線程(這里的主線程是相對的),等待這個異步方法執行完成並返回,接着才繼續執行主線程的方法。用了await,主線程才能得到異步方法返回的Task對象,以便於在主線程中對它進行操作。如果一個異步方法調用時沒有用await,那么它就會去異步執行,主線程不會等待,就像我們代碼段[3]中Main方法里對funcList方法的調用那樣。
最后就分析到這兒吧,希望各位兄弟博友能一起討論,共同進步。
當然,別吝嗇您的[贊]哦 :)
更新:現在大家看到的是改進之后的文章,在此謝謝文章下面幾位高手的討論,讓我學會了不少。抱拳!