msdn介紹:https://msdn.microsoft.com/zh-cn/library/hh191443.aspxhtml
其實很簡單,標記了async的方法爲異步方法,從方法的左大括號開始同步執行,直到第一個await出現就開始異步執行,主線程等待,等帶await這行代碼異步完了再回到主線程,而後繼續往下執行。
若是後面又遇到帶await語句的,又異步執行,執行完了就回來,繼續同步往下。依此類推。
這樣作其實就把咱們之前編寫等待句柄接收信號的代碼給省掉了,就一個await就搞定。web
Visual Basic 中的 Async 和 Await 關鍵字,以及 C# 中的 async 和 await 關鍵字都是異步編程的核心。 經過使用這兩個關鍵字,你可使用 .NET framework 或 Windows 運行時中的資源輕鬆建立異步方法(幾乎與建立同步方法同樣輕鬆)。 經過使用被稱爲異步方法的 async 和 await 定義的異步方法。express
如下特徵總結了使上一個示例成爲異步方法的緣由。編程
方法簽名包含一個 Async 或 async 修飾符。promise
按照約定,異步方法的名稱以「Async」後綴爲結尾。 app
返回類型爲下列類型之一: dom
若是你的方法有操做數爲 TResult 類型的返回語句,則爲 Task<TResult>。異步
若是你的方法沒有返回語句或具備沒有操做數的返回語句,則爲 Task。async
若是你編寫的是異步事件處理程序,則爲 Void(Visual Basic 中爲 Sub)。ide
有關詳細信息,請參見本主題後面的「返回類型和參數」。
方法一般包含至少一個 await 表達式,該表達式標記一個點,在該點上,直到等待的異步操做完成方法才能繼續。 同時,將方法掛起,而且控件返回到方法的調用方。 本主題的下一節將解釋懸掛點發生的狀況。
在異步方法中,可以使用提供的關鍵字和類型來指示須要完成的操做,且編譯器會完成其他操做,其中包括持續跟蹤控件以掛起方法返回等待點時發生的狀況。 一些常規流程(例如,循環和異常處理)在傳統異步代碼中處理起來可能很困難。 在異步方法中,元素的編寫頻率與同步解決方案相同且此問題獲得解決。
關係圖中的數值對應於如下步驟。
事件處理程序調用並等待 AccessTheWebAsync 異步方法。
AccessTheWebAsync 可建立 HttpClient 實例並調用 GetStringAsync 異步方法如下載網站內容做爲字符串。
GetStringAsync 中發生了某種狀況,該狀況掛起了它的進程。 可能必須等待網站下載或一些其餘阻止活動。 爲避免阻止資源,GetStringAsync 會將控制權出讓給其調用方 AccessTheWebAsync。
GetStringAsync 返回 Task<TResult>,其中 TResult 爲字符串,而且 AccessTheWebAsync 將任務分配給 getStringTask 變量。 該任務表示調用 GetStringAsync 的正在進行的進程,其中承諾當工做完成時產生實際字符串值。
因爲還沒有等待 getStringTask,所以,AccessTheWebAsync 能夠繼續執行不依賴於 GetStringAsync 得出的最終結果的其餘工做。 該任務由對同步方法 DoIndependentWork 的調用表示。
DoIndependentWork 是完成其工做並返回其調用方的同步方法。
AccessTheWebAsync 已用完工做,能夠不受 getStringTask 的結果影響。 接下來,AccessTheWebAsync 須要計算並返回該下載字符串的長度,但該方法僅在具備字符串時才能計算該值。
所以,AccessTheWebAsync 使用一個 await 運算符來掛起其進度,並把控制權交給調用 AccessTheWebAsync 的方法。 AccessTheWebAsync 將 Task(Of Integer) 或 Task<int> 返回至調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。
![]() |
---|
若是 GetStringAsync(所以 getStringTask)在 AccessTheWebAsync 等待前完成,則控件會保留在 AccessTheWebAsync 中。 若是異步調用過程 (AccessTheWebAsync) 已完成,而且 AccessTheWebSync 沒必要等待最終結果,則掛起而後返回到 getStringTask 將形成成本浪費。 |
在調用方內部(此示例中的事件處理程序),處理模式將繼續。 在等待結果前,調用方能夠開展不依賴於 AccessTheWebAsync 結果的其餘工做,不然就需等待片刻。 事件處理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
GetStringAsync 完成並生成一個字符串結果。 字符串結果不是經過按你預期的方式調用 GetStringAsync 所返回的。 (記住,該方法已返回步驟 3 中的一個任務)。 相反,字符串結果存儲在表示完成 getStringTask 方法的任務中。 await 運算符從 getStringTask 中檢索結果。 賦值語句將檢索到的結果賦給 urlContents。
當 AccessTheWebAsync 具備字符串結果時,該方法能夠計算字符串長度。 而後,AccessTheWebAsync 工做也將完成,而且等待事件處理程序可繼續使用。 在此主題結尾處的完整示例中,可確認事件處理程序檢索並打印長度結果的值。
若是你不熟悉異步編程,請花 1 分鐘時間考慮同步行爲和異步行爲之間的差別。 當其工做完成時(第 5 步)會返回一個同步方法,但當其工做掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。 在異步方法最終完成其工做時,任務會標記爲已完成,而結果(若是有)將存儲在任務中。
示例:
public class MyClass { public MyClass() { DisplayValue(); //這裏不會阻塞 System.Diagnostics.Debug.WriteLine("MyClass() End."); } public Task<double> GetValueAsync(double num1, double num2) { return Task.Run(() => { for (int i = 0; i < 1000000; i++) { num1 = num1 / num2; } return num1; }); } public async void DisplayValue() { double result = await GetValueAsync(1234.5, 1.01);//此處會開新線程處理GetValueAsync任務,而後方法立刻返回 //這以後的全部代碼都會被封裝成委託,在GetValueAsync任務完成時調用 System.Diagnostics.Debug.WriteLine("Value is : " + result); } }
現有已寫好的幫助類。使用很簡單,將方法名做爲參數傳進去就好了,最經常使用的是把很耗時的序列化函數傳進去,以避免阻塞UI進程,形成卡頓現象,影響用戶體驗。
public static class TaskAsyncHelper { /// <summary> /// 將一個方法function異步運行,在執行完畢時執行回調callback /// </summary> /// <param name="function">異步方法,該方法沒有參數,返回類型必須是void</param> /// <param name="callback">異步方法執行完畢時執行的回調方法,該方法沒有參數,返回類型必須是void</param> public static async void RunAsync(Action function, Action callback) { Func<System.Threading.Tasks.Task> taskFunc = () => { return System.Threading.Tasks.Task.Run(() => { function(); }); }; await taskFunc(); if (callback != null) callback(); } /// <summary> /// 將一個方法function異步運行,在執行完畢時執行回調callback /// </summary> /// <typeparam name="TResult">異步方法的返回類型</typeparam> /// <param name="function">異步方法,該方法沒有參數,返回類型必須是TResult</param> /// <param name="callback">異步方法執行完畢時執行的回調方法,該方法參數爲TResult,返回類型必須是void</param> public static async void RunAsync<TResult>(Func<TResult> function, Action<TResult> callback) { Func<System.Threading.Tasks.Task<TResult>> taskFunc = ()=> { return System.Threading.Tasks.Task.Run(()=> { return function(); }); }; TResult rlt = await taskFunc(); if(callback != null) callback(rlt); } }
異步方法返回類型有3種,void,Task和Task<T>,void儘可能不要使用。
原理剖析:
使用async void標記的方法有不一樣的錯誤處理語義。async Task或async Task<T>方法拋出異常時,異常會被捕獲並放到Task對象上。然而,標記爲async void的方法沒有Task對象,因此async void方法拋出的任何異常都會直接放到SynchronizationContext(異步上下文)上,它是在async void方法開始的時候激活的。下面是一個例子:
//async void 方法不能被捕獲的異常 private async void ThrowExceptionAsync() { throw new InvalidOperationException(); } public void AsyncVoidExceptions_CannotBeCaughtByCatch() { try { ThrowExceptionAsync(); } catch (Exception ) { //異常不會被捕獲 throw; } }
async void有不一樣的組成語法。返回Task或Task<T>的async方法可使用await Task.WhenAny或Task.WhenAll等輕易組合。而返回void的async方法沒有提供簡單的方式來通知它們已經完成的調用代碼。啓用若干個async void方法很容易,但不容易決定它們何時完成。async void方法開始和完成時會通知它們的SynchronizationContext,可是自定義的SynchronizationContext對於常規應用代碼是一個複雜的解決方案。
async void方法測試很困難。因爲錯誤處理和組合的差別,編寫調用async void方法的單元測試很困難。
很明顯,async void方法與async Task方法相比有不少劣勢,但在一個特殊場合頗有用,那就是異步的事件句柄。它們直接將異常拋出到SynchronizationContext,這與同步的事件句柄表現很類似。同步的事件句柄一般是私有的,所以它們不能被組合或者直接測試。我想採起的方法是在異步事件句柄中最小化代碼,好比,讓它await一個包含實際邏輯的async Task方法,代碼以下:
private async void button1_Click(object sender, EventArgs e) { await Button1ClickAsync(); } public async Task Button1ClickAsync() { //處理異步工做 await Task.Delay(1000); }
總之,對於async Task和async void,你應該更喜歡前者。async Task方法更容易錯誤處理,組合和測試。對於異步的事件句柄異常,必須返回void。
這句話的意思是,不要不通過認真考慮就混合同步和異步代碼。特別地,在異步代碼上使用Task.Wait或Task.Result是一個餿主意。
下面是一個簡單的例子:一個方法阻塞了異步方法的結果。在控制檯程序中會工做的很好,可是從GUI或者ASP.Net上下文中調用的時候就會死鎖。死鎖的實際緣由是當調用Task.Wait的時候進一步開啓了調用棧。
//阻塞異步代碼時的一個常見死鎖問題 public static class DeadlockDemo { private static async Task DelayAsync() { await Task.Delay(1000); } // 調用 GUI 或 ASP.NET 上下文的時候會形成死鎖 public static void Test() { // 開始延遲. var delayTask = DelayAsync(); // 等待延遲 delayTask.Wait(); } }
形成這種死鎖的根本緣由是等待處理上下文的方式。默認狀況下,當一個未完成的Task處於被等待狀態時,當前上下文會被捕獲而且當此任務完成時恢復該方法。這個上下文若是不爲null就是當前的SynchronizationContext,在這種狀況下,它是當前的TaskScheduler(任務調度者)。GUI 和ASP.NET應用有一個SynchronizationContext,它只容許一次運行一大塊代碼。當await完成時,它嘗試在捕獲的上下文內執行異步方法的剩餘部分。可是該上下文已經有一個線程了,它在(同步地)等待這個async方法的完成。它們每個都在等待另外一個,形成了死鎖。
注意控制檯程序不會形成這種死鎖。它們有個線程池SynchronizationContext而沒有一次執行一大坨代碼的SynchronizationContext,所以當await完成時,它在線程池線程上調度該async方法的剩餘部分。該方法能夠完成,它完成了返回task,並無死鎖。
總之,應該避免混合async和阻塞的代碼。這樣作的話會形成死鎖,更復雜的錯誤處理和上下文線程不可預測的阻塞。
能夠查看個人另外一篇博客《Async and Await 異步和等待》的「避免上下文」部分。
這裏稍加補充以下:
除了性能方面以外,ConfigureAwait還有另外一個重要的方面:它能夠避免死鎖。在「一直使用async」的代碼示例中,再次思考一下:若是你在DelayAsync代碼行添加「ConfigureAwait(false)」,那麼死鎖就會避免。此次,當await完成時,它嘗試在線程池上下文內執行async方法的剩餘部分。該方法能夠完成,完成後返回task,而且沒有死鎖。這項技術對於逐漸將應用從同步轉爲異步特別有用。
建議將ConfigureAwait用在方法中的每一個await以後。只有當未完成的Task被等待時,纔會喚起上下文被捕獲;若是Task已經完成了,那麼上下文不會被捕獲。
async Task MyMethodAsync() { //這裏的代碼運行在原始 context. await Task.FromResult(1); //這裏的代碼運行在原始 context. await Task.FromResult(1).ConfigureAwait(continueOnCapturedContext: false); // 這裏的代碼運行在原始 context. var random = new Random(); int delay = random.Next(2); // delay是 0 or 1 await Task.Delay(delay).ConfigureAwait(continueOnCapturedContext: false); // 這裏的代碼不肯定是否運行在原始 context. }
每一個異步方法都有本身的上下文,所以若是一個異步方法調用另外一個異步方法,那麼它們的上下文是獨立的。
private async Task HandleClickAsync() { // 這裏可使用ConfigureAwait await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false); } private async void button1_Click(object sender, EventArgs e) { button1.Enabled = false; try { // 這裏不能使用 ConfigureAwait await HandleClickAsync(); } finally { // 返回到這個方法的原始上下文 button1.Enabled = true; } }
今天就寫到這裏吧,還有不少很高級的用法,須要本身好好研究一下才能分享出來,但願你們多多支持!