【博主】反骨仔 【出處】http://www.cnblogs.com/liqingwen/p/5922573.html
通過使用異步編程,你可以避免性能瓶頸並增強應用程序的總體響應能力。
Visual Studio 2012 引入了一個簡化的方法,異步編程,在 .NET Framework 4.5 和 Windows 運行時利用異步支持。編譯器可執行開發人員曾進行的高難度工作,且應用程序保留了一個類似於同步代碼的邏輯結構。因此,您僅需要進行一小部分工作就可以獲得異步編程的所有優點。
異步對可能引起阻塞的活動(例如應用程序訪問 Web 時)至關重要。對 Web 資源的訪問有時很慢或會延遲。如果此類活動在同步過程中受阻,則整個應用程序必須等待。 在異步過程中,應用程序可繼續執行不依賴 Web 資源的其他工作,直至潛在阻塞的任務完成。
下圖顯示了異步編程提高響應能力的典型應用場景。包含從 .NET Framework 4.5 和 Windows 運行時中列出的一些包含支持異步編程的方法的類。
由於所有與用戶界面相關的活動通常共享一個線程,因此,異步對訪問 UI 線程的應用程序來說尤為重要。 如果在一個同步應用程序中有任何的線程被阻塞了,那麼所有線程都將被阻塞,再嚴重一點,你的應用程序將會停止響應。
使用異步方法時,應用程序將繼續響應 UI。例如,你可以調整窗口的大小或最小化窗口;如果你不希望等待應用程序結束,則可以將其關閉。
C# 中的 async 和 await 關鍵字都是異步編程的核心。通過使用這兩個關鍵字,你可以使用 .NET framework 或 Windows 運行時中的資源輕松創建異步方法(幾乎與創建同步方法一樣輕松)。
下面的示例演示了一種使用 async 和 await 定義的異步方法。
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 DoIndependentWork(); 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 時沒有其它操作,你可以用這樣的方式來簡化代碼。
string urlContents = await client.GetStringAsync("http://www.google.com.hk/");
根據以上代碼進行簡單總結:
(1)方法簽名包含一個 async 修飾符。
(2)按照約定,異步方法的名稱以“Async”後綴為結尾。
(3)返回類型為下列類型之一:
① 如果你的方法有操作數為 TResult 類型的返回語句,則為 Task<TResult>。
② 如果你的方法沒有返回語句或具有沒有操作數的返回語句,則為 Task。
③ 如果你編寫的是異步事件處理程序,則為 void。
(4)方法通常包含至少一個 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> 返回至調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。
【備注】如果 GetStringAsync(即 getStringTask)在 AccessTheWebAsync 等待前完成,則控制權會保留在 AccessTheWebAsync 中。 如果異步調用過程 (getStringTask) 已完成,並且 AccessTheWebSync 不必等待最終結果,則掛起然後返回到 AccessTheWebAsync,但這會造成成本的浪費。在調用方內部(假設這是一個事件處理程序),處理模式將繼續。在等待結果前,調用方可以開展不依賴於 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 表達式不會導致編譯器錯誤。如果異步方法未使用 await 運算符標記懸掛點,則該方法將作為同步方法執行,不管異步修飾符如何。編譯器將為此類方法發布一個警告。
如果方法包含 指定類型 TResult 的操作數的 return 語句,則將 Task<TResult> 指定為返回類型。
如果方法不含任何 return 語句或包含不返回操作數的 return 語句,則將 Task 用作返回類型。
下面的示例演示如何聲明並調用可返回 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 返回類型的異步方法,並且一個 void 返回值的調用方無法捕獲該方法引發的任何異常。
異步方法無法聲明 C# 中的 ref 或 out 參數,但此方法可以調用具有此類參數的方法。
如果某一約定中的事件、基類或接口協定建議其他名稱,則可以忽略此約定。例如,你不應重命名常用事件處理程序,例如 btnOpen_Click。
【參考引用】微軟官方文檔圖片