感謝Marco CAO指出的兩點錯誤,已做出修改與補充
異步函數(async/await)簡單應用
.NET Framework4.5提供了針對異步函數語法糖,簡化了編寫異步函數的復雜度。
下面通過一個簡單的示例,介紹.NET Framework4.5對異步函數的支持。
窗體頁面
窗體代碼

public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void btnGetText_Click(object sender, EventArgs e) { string urlString = string.Empty; using (HttpClient client = new HttpClient()) { //異步獲取“http://10.32.112.82/epms/”內容(線程返回)。用戶界面依舊可以操作 urlString = await client.GetStringAsync(@"http://10.32.112.82/epms/"); } txtUrlString.Text = urlString; } }
說明
1.點擊獲取url,程序開始獲取url內容。(注意:這里我寫的url是我們公司內部的網址,必須通過代理才可以訪問,所以肯定會有延遲效果。)
2.雖然有延遲效果,但是窗體依舊可以操作(在第一個textBox中可以輸入內容)。表明窗體並沒有被阻塞,也就是說獲取url內容是異步操作。
3.一段時間后,你會發現窗體報錯,表明獲取url內容操作完畢,操作失敗。
講解
1.異步函數的關鍵字:async;等待異步結果的關鍵字:await
2.異步方法的調用,與獲取異步方法的結果,在同一個方法體內。這點極大了簡化異步方法維護的成本。(以前BeginInvoke()、EndInvoke(),起碼是2個方法,且不能在同一方法體內調用。下文異步函數簡化APM模型有介紹。)
3.await作用的方法表示:【進入該方法的線程】會返回線程池,程序會在此掛起等待返回結果(這里需要注意,因為程序得到返回結果再執行時,是從線程池中另起了一個線程在執行),同時【子線程】會執行await后面的方法。(這點是:async與await的核心)
異步函數(async/await)實現異步原理(狀態機)
async/await關鍵點:
1.有幾個async方法,就創建幾個【狀態機】。
2.【狀態機】的幾種狀態(-1:初始化;-2:方法體執行結束;0:第一個await方法;1:第二個await方法;。。。依次往下)
3.【狀態機】控制着異步方法執行流程(MoveNext方法)
通過上面的介紹,我們知道【狀態機】控制着方法執行流程,下面我們就來對【狀態機】如何控制方法執行流程一探究竟。
下面這段測試代碼,來自:CLR via C#(第4版)
示例代碼

private static async Task<string> MyMethodAsync(int argument) { int local = argument; try { Type1 result1 = await Method1Async(); for (int x = 0; x < 3; x++) { Type2 result2 = await Method2Async(); } } catch (Exception) { Console.WriteLine("Catch"); } finally { Console.WriteLine("Finally"); } return "Done"; } private static async Task<Type1> Method1Async() { string a = await Task.Run<string>(() => "Method1"); return new Type1(); } private static async Task<Type2> Method2Async() { string a = await Task.Run<string>(() => "Method2"); return new Type2(); } sealed class Type1 { } sealed class Type2 { }
用Reflector.exe反編譯后,得到如下代碼(MyMethodAsync)

[CompilerGenerated] private struct <MyMethodAsync>d__9 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder<string> <>t__builder; private object <>t__stack; private TaskAwaiter<Program.Type1> <>u__$awaitere; private TaskAwaiter<Program.Type2> <>u__$awaiterf; public int <local>5__a; public Program.Type1 <result1>5__b; public Program.Type2 <result2>5__d; public int <x>5__c; public int argument; // Methods private void MoveNext() { string str; try { bool flag = true; switch (this.<>1__state) { case -3: goto Label_01E1; case 0: case 1: break; default: this.<local>5__a = this.argument; break; } try { switch (this.<>1__state) { } try { TaskAwaiter<Program.Type1> awaiter; switch (this.<>1__state) { case 0: break; case 1: goto Label_0137; default: awaiter = Program.Method1Async().GetAwaiter(); if (awaiter.IsCompleted) { goto Label_00D7; } this.<>1__state = 0; this.<>u__$awaitere = awaiter; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<Program.Type1>, Program.<MyMethodAsync>d__9>(ref awaiter, ref this); flag = false; return; } awaiter = this.<>u__$awaitere; this.<>u__$awaitere = new TaskAwaiter<Program.Type1>(); this.<>1__state = -1; Label_00D7: Program.Type1 introduced11 = awaiter.GetResult(); awaiter = new TaskAwaiter<Program.Type1>(); Program.Type1 type = introduced11; this.<result1>5__b = type; this.<x>5__c = 0; while (this.<x>5__c < 3) { TaskAwaiter<Program.Type2> awaiter3 = Program.Method2Async().GetAwaiter(); if (awaiter3.IsCompleted) { goto Label_0156; } this.<>1__state = 1; this.<>u__$awaiterf = awaiter3; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<Program.Type2>, Program.<MyMethodAsync>d__9>(ref awaiter3, ref this); flag = false; return; Label_0137: awaiter3 = this.<>u__$awaiterf; this.<>u__$awaiterf = new TaskAwaiter<Program.Type2>(); this.<>1__state = -1; Label_0156: Program.Type2 introduced12 = awaiter3.GetResult(); awaiter3 = new TaskAwaiter<Program.Type2>(); Program.Type2 type2 = introduced12; this.<result2>5__d = type2; this.<x>5__c++; } } catch (Exception) { Console.WriteLine("Catch"); } } finally { if (flag) { Console.WriteLine("Finally"); } } str = "Done"; } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } Label_01E1: this.<>1__state = -2; this.<>t__builder.SetResult(str); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine param0) { this.<>t__builder.SetStateMachine(param0); } }
這里我要說一句抱歉,這里貼出的代碼可讀性很差(沒有關鍵字,沒有着色)。本來想截圖的,但是由於反編譯的代碼很長,無法在我顯示器中一屏顯示完,所以我也沒截圖。下面講解的時候,采用分段截圖來說明,希望大家看得明白。同時也推薦大家自己用Reflector.exe反編譯后,自己閱讀。
代碼閱讀
1.【狀態機】初始化(-1)
2.【狀態機】Start入口-》MoveNext方法
3.【狀態機】個數(3個async方法,所以3個狀態機)
4.核心代碼(MoveNext方法)
這段代碼的大致意思是:<>1__state初始狀態為-1,進入default分支,判斷Method1Async()是否執行完畢,若未執行完畢,<>1__state設計為0(表示返回第一個await對應的方法塊),調用了一個方法(紅框標注),然后return(線程返回線程池,函數掛起等待返回結果)。若執行完畢,【goto Label_00D7】正好就是獲取第一個await結果的代碼塊。
下面重點關注一下,紅框標注的代碼(AsyncTaskMethodBuilder<string>.AwaitUnsafeOnCompleted())做了什么?
依舊需要借助反編碼工具,查到這個調用的真實代碼,如下
第一句是關鍵,方法名稱:得到完成時調用的委托。進入方法一探究竟
AsyncTaskMethodBuilder.GetCompletionAction()代碼如下
紅框的批注,action就是完成時調用的方法,進入要MoveNextRunner.Run()方法中。
真相大白,action就是狀態機的MoveNext()方法。
總結:AsyncTaskMethodBuilder<string>.AwaitUnsafeOnCompleted()做得事就是,當異步函數執行完畢后,回調狀態機的MoveNext()方法
異步函數(async/await)簡化異步編程模型(APM)
APM編程代碼

static void Main(string[] args) { AsyncAPM(); } static void AsyncAPM() { Console.WriteLine("Main thread ID={0}", Thread.CurrentThread.ManagedThreadId); byte[] s_data = new byte[100]; FileStream fs = new FileStream(@"d:\1.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous); fs.BeginRead(s_data, 0, s_data.Length, ReadIsDone, fs); Console.WriteLine("主線程執行完畢"); Console.ReadLine(); } private static void ReadIsDone(IAsyncResult ar) { Thread.Sleep(5000); Console.WriteLine("ReadIsDone thread ID={0}", Thread.CurrentThread.ManagedThreadId); FileStream fs = (FileStream)ar.AsyncState; int bytesRead = fs.EndRead(ar); fs.Close(); Console.WriteLine("Number of bytes read={0}", bytesRead); }
這種編程模式有一個很大的缺點:如果我想【獲取異步返回的結果,然后打印出來】。那么【獲取異步返回結果,然后打印出來】必須放在回調函數中,也就是另一塊代碼中(上面的實例,獲取字節數,然后打印出來。就必須放在ReadIsDone函數中),這其實給閱讀代碼增添了難度(如果多個異步函數,閱讀代碼時就要在很多方法中來回跳轉)。當然你也可以在一個方法中用EndInvoke()來做等待。這樣雖然也是異步,但是會出現主程序阻塞,等待異步返回結果。
異步函數簡化代碼

static void Main(string[] args) { SimplifyAsyncToAPM(); Console.ReadLine(); } static async void SimplifyAsyncToAPM() { int length = await AsyncToAPM(); Console.WriteLine("File length={0}", length); } static async Task<int> AsyncToAPM() { byte[] s_data = new byte[100]; FileStream fs = new FileStream(@"d:\1.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous); return await Task.Factory.FromAsync<byte[], int, int, int>(fs.BeginRead, fs.EndRead, s_data, 0, s_data.Length, fs); }
異步函數(async/await)應用於事件編程模型
示例代碼(讀取網絡中一張圖片的字節數)

static void Main(string[] args) { Task<int> result = AsyncEvent(new Uri("http://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%9B%BE%E7%89%87&pn=0&spn=0&di=84991311930&pi=&rn=1&tn=baiduimagedetail&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=3063552411%2C3030228420&os=1484689785%2C3968535026&simid=0%2C0&adpicid=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=11&oriquery=&objurl=http%3A%2F%2Fwww.52ij.com%2Fuploads%2Fallimg%2F160317%2F1110104P8-4.jpg&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bcdt3_z%26e3Bv54AzdH3F25g2xtw5AzdH3F9cmaa0_z%26e3Bip4s&gsm=0")); Console.WriteLine(result.Result); } static async Task<int> AsyncEvent(Uri uri) { WebClient wc = new WebClient(); TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); wc.DownloadDataCompleted += (s, e) => { if (e.Cancelled) tcs.SetCanceled(); else if (e.Error != null) tcs.SetException(e.Error); else tcs.SetResult(e.Result.Count()); }; wc.DownloadDataAsync(uri); return await tcs.Task; }
感謝大家的耐心閱讀