[.NET] 利用 async & await 的異步編程

利用 async & await 的異步編程

【博主】反骨仔    【出處】http://www.cnblogs.com/liqingwen/p/5922573.html   html

目錄

 

1、異步編程的簡介

  經過使用異步編程,你能夠避免性能瓶頸並加強你的應用程序的整體響應能力。web

  從 VS 2012 開始,新引入了一個簡化的方法,稱爲異步編程。咱們在 >= .NET 4.5 中和 Windows 運行時中使用異步,編譯器它會幫助了咱們下降了曾經進行的高難度異步代碼編寫的工做,但邏輯結構卻相似於同步代碼。所以,咱們僅須要進行一小部分編程的工做就能夠得到異步編程的全部優勢。express

 

2、異步提升響應能力

  異步對可能引發阻塞的活動(如訪問 Web 時),對 Web 資源的訪問有時過慢或延遲太高。若這種任務在同步過程當中受阻,則整個應用程序必須等待響應完成。 在使用異步的過程當中,咱們的應用程序可繼續執行不依賴 Web 資源的其餘工做,並會一直等待阻塞的任務順利完成。編程

  這是一些典型的使用異步的應用場景,以及一些在 .NET >= 4.5 後新增的類庫。windows

  全部與用戶界面相關的操做一般共享一個線程,因此使用異步對於使用 UI 線程的 App 來講是很是重要的。promise

  若是說你的 App 全部操做都是同步的,也就是說,當一個線程出現阻塞,其它線程都會出現阻塞,更嚴重的是, App 會中止響應。多線程

 

  使用異步方法時,App 將繼續響應 UI。如:最大和最小化,可是功能依然在後臺執行(如:下載)。
app

 

3、更容易編寫的異步方法

  C# 中的 async 和 await 關鍵字都是異步編程的核心經過使用這兩個關鍵字,咱們就能夠在 .NET 輕鬆建立異步方法。
異步

  示例:
async

 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 就能夠了,剩餘的其它事情,就留給編譯器吧。 

 

4、異步方法的控制流(核心)

  異步編程中最重要卻不易懂的是控制流,即不一樣方法間的切換。如今,請用一顆感恩的心來觀察下圖。


  步驟解析:

  ① 事件處理程序調用並等待 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> 返回至調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。

  【備註】若是 GetStringAsync(即 getStringTask)在 AccessTheWebAsync 等待前完成,則控制權會保留在 AccessTheWebAsync 中。 若是異步調用過程 (getStringTask) 已完成,而且 AccessTheWebSync 沒必要等待最終結果,則掛起而後返回到 AccessTheWebAsync,但這會形成成本的浪費。

  在調用方內部(假設這是一個事件處理程序),處理模式將繼續。在等待結果前,調用方能夠開展不依賴於 AccessTheWebAsync 結果的其餘工做,不然就需等待片刻。事件處理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。

  ⑦ GetStringAsync 完成並生成一個字符串結果。 字符串結果不是經過你預期的方式調用 GetStringAsync 所返回的。(請記住,此方法已在步驟 3 中返回一個任務。)相反,字符串結果存儲在表示完成方法 getStringTask 的任務中。 await 運算符從 getStringTask 中檢索結果。賦值語句將檢索到的結果賦給 urlContents。

  ⑧ 當 AccessTheWebAsync 具備字符串結果時,該方法能夠計算字符串長度。而後,AccessTheWebAsync 工做也將完成,而且等待事件處理程序可繼續使用。 

 

  你能夠嘗試思考一下同步行爲和異步行爲之間的差別。當其工做完成時(第 5 步)會返回一個同步方法,但當其工做掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。在異步方法最終完成其工做時,任務會標記爲已完成,而結果(若是有)將存儲在任務中。

 

5、異步中的線程

  異步方法旨在成爲非阻塞操做異步方法中的 await 表達式在等待的任務執行的同時不會阻塞當前線程。相反,await 表達式在繼續執行時方法的其他部分並將控制權返回到異步方法的調用方。

  async 和 await 關鍵字不會致使建立其餘線程由於異步方法不會在其自身線程上運行,所以它不須要多線程。只有當方法處於活動狀態時,該方法將在當前同步上下文中運行並使用線程上的時間。可使用 Task.Run 將佔用大量 CPU 的工做移到後臺線程,可是後臺線程不會幫助正在等待結果的進程變爲可用狀態。

  對於異步編程而言,該基於異步的方法優於幾乎每一個用例中的現有方法。具體而言,此方法比 BackgroundWorker 更適用於 IO 綁定的操做,由於此代碼更簡單且無需防止搶先爭用條件。結合 Task.Run() 使用時,異步編程比 BackgroundWorker 更適用於 CPU 綁定的操做,由於異步編程將運行代碼的協調細節與 Task.Run 傳輸至線程池的工做區分開來。

 

6、async 和 await 修飾符

  當你使用 async 修飾符指定該方法爲異步方法時:
  • 可使用 await 來指定懸掛點。await 運算符會告訴編譯器,異步方法只有直到等待的異步過程執行完成,才能繼續經過該點往下執行。同時,控制權將返回至異步方法的調用方。await 表達式中異步方法在掛起後,若是該方法尚未執行完成並退出,finally 塊中的將不會執行。

  • 標記的異步方法自己能夠經過調用它的方法進行等待。異步方法中一般包含一個或多個 await 運算符,固然,一個 await 表達式都不存在也不會致使編譯器錯誤,可是編譯器會發出警告,該方法在執行的時候依然會依照同步方法來執行,async 其實只是一個標識的做用而已,告訴編譯器他「應該」是一個異步方法。

 

7、返回類型和參數信息

  在編寫異步方法時,咱們絕大部分會使用 Task 和 Task<TResult> 做爲返回類型

 

  示例:

 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 關鍵字,可是異步方法能夠調用包含這些參數的方法。

 

8、命名的約定

  根據約定,使用 async 的方法都應該以「Async」做爲後綴,如:DownloadAsync()  可是,若是某一約定中的事件、基類或接口有其餘的形式約定,則能夠忽略上述約定。 例如,不該該修改或重命名經常使用事件處理程序,如 btnOpen_Click。

 

傳送門 

  1. 走進異步編程的世界 - 開始接觸 async/await(推薦)

  2. 走進異步編程的世界 - 剖析異步方法(上)

  3. 走進異步編程的世界 - 剖析異步方法(下)

  4. 走進異步編程的世界 - 在 GUI 中執行異步操做

 


【參考引用】微軟官方文檔圖片

【參考】https://msdn.microsoft.com/zh-cn/library/windows/apps/hh191443(v=vs.110).aspx

相關文章
相關標籤/搜索