通過前面的文章,已經學習了怎麼使用線程,怎麼使用線程同步,怎麼使用線程池,怎麼使用任務並行庫。儘管通過上面的學習,對於線程的使用越來越簡單。有沒有更簡單的方法呢。 C# 5.0之後,微軟在c#語言中添加了兩個關鍵字async與await,這是在TPL上面的更高一級的抽象,真正簡化了異... ...
通過前面的文章,已經學習了怎麼使用線程,怎麼使用線程同步,怎麼使用線程池,怎麼使用任務並行庫。儘管通過上面的學習,對於線程的使用越來越簡單。有沒有更簡單的方法呢。
C# 5.0之後,微軟在c#語言中添加了兩個關鍵字async與await,這是在TPL上面的更高一級的抽象,真正簡化了非同步編程的編程方式,從而有助於我們編寫出真正健壯少bug的非同步應用程式。下麵我先來看一個最簡單的示例。
async Task<string> AsyncHello() { await Task.Delay(TimeSpan.FromSeconds(2)); Return “ Hello world”; }
使用async標記非同步函數,建議返回async Task<T>。
Await只能使用在有async標誌的方法內部。在async標記的方法內部最少要有一個await,當然,如果一個也沒有,編譯也不會報錯,但是會有編譯警告。如下圖。
上面的代碼在執行完await調用的代碼之行後該方法會直接返回。如果同步執行,執行線程會阻塞2秒之後返回結果,本示例里在執行完await操作後,立即將工作線程放回線程池中,我們會非同步等待。2秒後,我們會從線程池中取得工作線程並繼續運行其中剩餘的非同步方法。這就允許我們在等待的2秒的時間里可以重用線程池中的線程,這對提高應用程式的可伸縮性非常重要。通過使用async與await我們擁有了線性的程式控制流程,但是執行過程卻是非同步的。
一、 使用await獲取非同步操作結果
本示例是學習await如何獲取非同步操作結果。同時會與TPL進行比較。
1.示例代碼如下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace ThreadAsyncDemo { class Program { static void Main(string[] args) { Task t = AsyncWithTPL(); t.Wait(); t = AsyncWithAwait(); t.Wait(); Console.Read(); } static Task AsyncWithTPL() { Task<string> task1 = GetInfoAsync("Task 1"); Task task2 = task1.ContinueWith(task => Console.WriteLine(task1.Result), TaskContinuationOptions.NotOnFaulted); Task task3 = task1.ContinueWith(task => Console.WriteLine(task1.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted); return Task.WhenAny(task2, task1); } async static Task AsyncWithAwait() { try { string result = await GetInfoAsync("Task 4"); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(ex.Message); } } async static Task<string> GetInfoAsync(string name) { await Task.Delay(TimeSpan.FromSeconds(2)); //throw new Exception("拋出異常信息!"); return string.Format(" Task {0} 正在運行線上程 ID={1}上。這個工作線程是否是線程池中的線程:{2}", name,
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } } }
2.程式運行的結果如下圖。
程式同時運行了兩個非同步操作。其中一個是標準的TPL代碼,另一個使用了async與await兩個關鍵字。AsyncWithTPL啟動了一個任務,運行兩秒之後返回關於工作線程信息的字元串。然後我們定義了一個後續操作,用於在非同步操作完成後列印出操作結果,還有另一個後續操作,用於萬一有錯誤時,列印出異常信息。最終返回了一個代表其中一個後續操作任務的任務,並等等其在主函數中完成。
在asyncWithAwait方法中,我們對任務使用await並得到了相同 的結果。這和編寫普通的同步代碼的風格一樣,即我們獲取了任務的結果,列印了出來,如果任務完成時帶有錯誤則捕獲異常。關鍵不同的是這實際上是一個非同步操作。使用await後,c#立即創建了一個任務,其中一個有後續操作任務,包含了await操作符後面的所有剩餘代碼。這個新任務也處理了異常。然後這個任務返回 到主方法並等待共完成 。
因此可以看出程式中的兩段代碼在概念上是相同的,使用await由編譯 器隱式地處理了非同步代碼。
3. 我們把上面註釋的拋出異常的代碼,取消註釋,然後運行程式。得到如下圖的結果。
註:在gui與asp.net之類的環境 中不推薦 使用task.wait和taskResult方法同,因為如果代碼寫的不好,很容易導致死鎖。
二、 在Lambda表達式中使用await操作符
本示例學習如何在lambda表達式中使用await。將學習如何編寫一個使用了await的匿名方法,並且獲取非同步執行該方法的結果。
1.示例代碼如下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace ThreadAsyncDemo { class Program { static void Main(string[] args) { Task t = AsyncProcess(); t.Wait(); Console.Read(); } async static Task AsyncProcess() { Func<String, Task<string>> asyncLambda = async name => { await Task.Delay(TimeSpan.FromSeconds(2)); return string.Format(" Task {0} 正在運行線上程 ID={1}上。這個工作線程是線程池的線程:{2}" ,name,
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); }; string result = await asyncLambda("async lambda"); Console.WriteLine(result); } } }
2.程式運行結果,如下圖。
首先不能在main方法中使用async,我們將非同步函數移到了asyncProcess中,然後使用async關鍵字聲明瞭一個lambda表達式。由於 任何lambda表達式的類型都不能通過lambda自身來推斷,所以不得不顯示地指定類型為一字元串,並返回一個Task<string>對象 。
然後,我們定義 了lambda表達式體,這個方法雖然定義返回的是一個Task<string>對象 ,但實際上返回的是字元串,卻沒有編譯錯誤。這是因為c#編譯器自動 產生了一個任務並返回給我們。
最後一步就是列印出lambda 表達式執行後的結果。