利用 async & await 的異步編程
【博主】反骨仔 【出處】http://www.cnblogs.com/liqingwen/p/5922573.html
目錄
一、異步編程的簡介
通過使用異步編程,你可以避免性能瓶頸並增強你的應用程序的總體響應能力。
從 VS 2012 開始,新引入了一個簡化的方法,稱為異步編程。我們在 >= .NET 4.5 中和 Windows 運行時中使用異步,編譯器它會幫助了我們降低了曾經進行的高難度異步代碼編寫的工作,但邏輯結構卻類似於同步代碼。因此,我們僅需要進行一小部分編程的工作就可以獲得異步編程的所有優點。
二、異步提高響應能力
異步對可能引起阻塞的活動(如訪問 Web 時),對 Web 資源的訪問有時過慢或延遲過高。若這種任務在同步過程中受阻,則整個應用程序必須等待響應完成。 在使用異步的過程中,我們的應用程序可繼續執行不依賴 Web 資源的其他工作,並會一直等待阻塞的任務順利完成。
這是一些典型的使用異步的應用場景,以及一些在 .NET >= 4.5 后新增的類庫。
所有與用戶界面相關的操作通常共享一個線程,所以使用異步對於使用 UI 線程的 App 來說是非常重要的。
如果說你的 App 所有操作都是同步的,也就是說,當一個線程出現阻塞,其它線程都會出現阻塞,更嚴重的是, App 會停止響應。
使用異步方法時,App 將繼續響應 UI。如:最大和最小化,但是功能依然在后台執行(如:下載)。
三、更容易編寫的異步方法
C# 中的 async 和 await 關鍵字都是異步編程的核心。通過使用這兩個關鍵字,我們就可以在 .NET 輕松創建異步方法。
示例:
1 /// <summary> 2 /// 異步訪問 Web 3 /// </summary> 4 /// <returns></returns> 5 /// <remarks> 6 /// 方法簽名的 3 要素: 7 /// ① async 修飾符 8 /// ② 返回類型 Task 或 Task<TResult>:這里的 Task<int> 表示 return 語句返回 int 類型 9 /// ③ 方法名以 Async 結尾 10 /// </remarks> 11 async Task<int> AccessTheWebAsync() 12 { 13 //記得 using System.Net.Http 哦 14 var client = new HttpClient(); 15 16 //執行異步方法 GetStringAsync 17 Task<string> getStringTask = client.GetStringAsync("http://www.google.com.hk/"); 18 19 //假設在這里執行一些非異步的操作 20 Do(); 21 22 //等待操作掛起方法 AccessTheWebAsync 23 //直到 getStringTask 完成,AccessTheWebAsync 方法才會繼續執行 24 //同時,控制將返回到 AccessTheWebAsync 方法的調用方 25 //直到 getStringTask 完成后,將在這里恢復控制。 26 //然后從 getStringTask 拿到字符串結果 27 string urlContents = await getStringTask; 28 29 //返回字符串的長度(int 類型) 30 return urlContents.Length; 31 }
如果 AccessTheWebAsync 在調用 GetStringAsync() 時沒有其它操作(如:代碼中的 Do()),你可以用這樣的方式來簡化代碼。
string urlContents = await client.GetStringAsync("http://www.google.com.hk/");
簡單總結:
(1)方法簽名包含一個 async 修飾符。
(2)根據約定,異步方法的名稱需要以“Async”后綴為結尾。
(3)3 種返回類型:
① Task<TResult>:返回 TResult 類型。
② Task:沒有返回值,即返回值為 void。
③ void:只適用於異步事件處理程序。
(4)方法通常包含至少一個 await 表達式,該表達式標記一個點,我們可以成為懸掛點,在該點上,直到等待的異步操作完成,之后的方法才能繼續執行。 與此同時,該方法將掛起,並將控制權返回到方法的調用方。
需要使用異步方法的話,我們直接在系統內部使用所提供的關鍵字 async 和 await 就可以了,剩余的其它事情,就留給編譯器吧。
四、異步方法的控制流(核心)
異步編程中最重要卻不易懂的是控制流,即不同方法間的切換。現在,請用一顆感恩的心來觀察下圖。
步驟解析:
① 事件處理程序調用並等待 AccessTheWebAsync() 異步方法。
② AccessTheWebAsync 創建 HttpClient 對象並調用它的 GetStringAsync 異步方法來下載網站內容。
③ 假設 GetStringAsync 中發生了某種情況,該情況掛起了它的進程。可能必須等待網站下載或一些其他阻塞的活動。為避免阻塞資源,GetStringAsync() 會將控制權出讓給其調用方 AccessTheWebAsync。GetStringAsync 返回 Task,其中 TResult 為字符串,並且 AccessTheWebAsync 將任務分配給 getStringTask 變量。該任務表示調用 GetStringAsync 的正在進行的進程,其中承諾當工作完成時產生實際字符串值。
④ 由於尚未等待 getStringTask,因此,AccessTheWebAsync 可以繼續執行不依賴於 GetStringAsync 得出最終結果的其他任務。該任務由對同步方法 DoIndependentWork 的調用表示。
⑤ DoIndependentWork 是完成其工作並返回其調用方的同步方法。
⑥ AccessTheWebAsync 已完成工作,可以不受 getStringTask 的結果影響。 接下來,AccessTheWebAsync 需要計算並返回該下載字符串的長度,但該方法僅在具有字符串時才能計算該值。因此,AccessTheWebAsync 使用一個 await 運算符來掛起其進度,並把控制權交給調用 AccessTheWebAsync 的方法。AccessTheWebAsync 將 Task<int> 返回至調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。
在調用方內部(假設這是一個事件處理程序),處理模式將繼續。在等待結果前,調用方可以開展不依賴於 AccessTheWebAsync 結果的其他工作,否則就需等待片刻。事件處理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
⑦ GetStringAsync 完成並生成一個字符串結果。 字符串結果不是通過你預期的方式調用 GetStringAsync 所返回的。(請記住,此方法已在步驟 3 中返回一個任務。)相反,字符串結果存儲在表示完成方法 getStringTask 的任務中。 await 運算符從 getStringTask 中檢索結果。賦值語句將檢索到的結果賦給 urlContents。
⑧ 當 AccessTheWebAsync 具有字符串結果時,該方法可以計算字符串長度。然后,AccessTheWebAsync 工作也將完成,並且等待事件處理程序可繼續使用。
你可以嘗試思考一下同步行為和異步行為之間的差異。當其工作完成時(第 5 步)會返回一個同步方法,但當其工作掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。在異步方法最終完成其工作時,任務會標記為已完成,而結果(如果有)將存儲在任務中。
五、異步中的線程
async 和 await 關鍵字不會導致創建其他線程。因為異步方法不會在其自身線程上運行,因此它不需要多線程。只有當方法處於活動狀態時,該方法將在當前同步上下文中運行並使用線程上的時間。可以使用 Task.Run 將占用大量 CPU 的工作移到后台線程,但是后台線程不會幫助正在等待結果的進程變為可用狀態。
對於異步編程而言,該基於異步的方法優於幾乎每個用例中的現有方法。具體而言,此方法比 BackgroundWorker 更適用於 IO 綁定的操作,因為此代碼更簡單且無需防止搶先爭用條件。結合 Task.Run() 使用時,異步編程比 BackgroundWorker 更適用於 CPU 綁定的操作,因為異步編程將運行代碼的協調細節與 Task.Run 傳輸至線程池的工作區分開來。
-
可以使用 await 來指定懸掛點。await 運算符會告訴編譯器,異步方法只有直到等待的異步過程執行完成,才能繼續通過該點往下執行。同時,控制權將返回至異步方法的調用方。await 表達式中異步方法在掛起后,如果該方法還沒有執行完成並退出,finally 塊中的將不會執行。
-
標記的異步方法本身可以通過調用它的方法進行等待。異步方法中通常包含一個或多個 await 運算符,當然,一個 await 表達式都不存在也不會導致編譯器錯誤,但是編譯器會發出警告,該方法在執行的時候依然會依照同步方法來執行,async 其實只是一個標識的作用而已,告訴編譯器他“應該”是一個異步方法。
示例:
1 static async Task<Guid> Method1Async() //Task<Guid> 2 { 3 var result = Guid.NewGuid(); 4 5 await Task.Delay(1); 6 7 //這里返回一個 Guid 的類型 8 return result; 9 } 10 11 static async Task Method2Async() //Task 12 { 13 //Do... 14 15 await Task.Delay(1); 16 17 //Do... 18 19 //這里沒有 return 語句 20 }
1 //調用 Method1Async 2 //方式一 3 Task<Guid> t1 = Method1Async(); 4 Guid guid1 = t1.Result; 5 6 //方式二 7 Guid guid2 = await Method1Async(); 8 9 //調用 Method2Async 10 //方式一 11 Task t2 = Method2Async(); 12 await t2; 13 14 //方式二 15 await Method2Async();
每個返回的任務表示正在進行的工作。任務可封裝有關異步進程狀態的信息,如果未成功,則最后會封裝來自進程的最終結果,或者是由該進程引發的異常。
【疑問】那么 void 返回類型是在什么情況下才使用的呢?
主要用於異步的事件處理程序,異步事件處理程序通常作為異步程序的起始點。void 返回類型告訴了編譯器,無需對他進行等待,並且,對於 void 返回類型的方法,我們也無法對他進行異常的捕捉。
異步方法不能夠在參數中聲明與使用 ref 和 out 關鍵字,但是異步方法可以調用包含這些參數的方法。
八、命名的約定
傳送門
【參考引用】微軟官方文檔圖片
【參考】https://msdn.microsoft.com/zh-cn/library/windows/apps/hh191443(v=vs.110).aspx