1 異步編程的重要性
C#5.0最重要的改進是提供了更強大的異步編程,C#5.0僅增加兩個關鍵字Async和Await,使用異步編程,方法調用是后台運行(通常在線程和任務的幫助下),並且不會阻塞調用線程。
2 異步模式
從.net1.0開始就提供了異步特性,而且.NET Framework的許多類都實現了一個或多個異步模式(委托也實現了異步模式)。因為在WIndows From和WPF中用異步模式更新界面比較復雜,所以在2.0中提供了基於事件的異步模式。現在在4.5中推出了另外一種新的方式來實現異步編程:基於任務的異步模式。這種模式是基於4.0中新增的Task類型,和一個利用Async和Await關鍵字的編譯器功能。
2.1 同步調用
1 WebClient wc = new WebClient(); 2 string s= wc.DownloadString("你的URL");
運行該代碼,程序一直在等待DownloadString的完成,在這種情況下使用異步非常有必要的。
2.2異步模式
使用異步模式是進行異步調用的方式之一,實現異步模式定義BeginXXX和EndXXX方法。如果有一個同步方法DownloadStrring,異步方法將轉化成了兩個方法BeginDownloadString和EndDownloadString方法。BeginXXX方法接受同步方法的所有輸入的參數,並且還定義了一個AsyncCallBack參數,用於接收在異步方法執行完畢后的被調用的委托,該方法返回一個IAsyncResult,用於驗證調用是否已經完成,並且一直等待,直到方法的執行完畢。EndXXX方法同步方法的所有的輸出參數,並按照同步方法的返回類型來返回結果。WebClient類沒有實現異步模式,但可以用HttpWebRequest來代替
1 HttpWebRequest req =(HttpWebRequest)WebRequest.Create("你的URL"); 2 IAsyncResult result= req.BeginGetResponse("",); 3 req.EndGetResponse();
異步模式的優勢是使用委托功能就能實現異步編程,不用改變程序的行為,也不會阻塞界面的操作。
2.3基於事件的異步模式
基於事件的異步模式定義了一個帶有"Aysnc"后綴的方法。異步方法完成時不是定義被調用的委托,而是定義事件。
1 WebClient client = new WebClient(); 2 client.DownloadStringCompleted += (sender, e1) => { }; 3 client.DownloadStringAsync(new Uri("你的URL"));
基於事件的異步方式的優勢是易用。
2.4基於任務的異步模式
1 public async void Fun() 2 { 3 WebClient client = new WebClient(); 4 string s = await client.DownloadStringTaskAsync("你的URL"); 5 }
現在代碼簡單多了,並且沒有阻塞。
3 異步編程的基礎
async和await關鍵字只是編譯器功能,編譯器會用Task創建代碼。
3.1創建任務
1 static string Greeting(string name) 2 { 3 Thread.Sleep(2000); 4 return string.Format("Hello ,{0}", name); 5 } 6 static Task<string> GreetingAsync() 7 { 8 return Task.Run<string>(()=> { return Greeting(name); }); 9 }
3.2調用異步方法
可以使用Await關鍵字來調用返回任務的異步方法GreetingAsync,使用Await關鍵字需要使用Async修飾聲明的方法。在完成GreetingAsync方法前該方法內的其它代碼不會被執行。
1 private async static void CallWithAsync() 2 { 3 string result = await GreetingAsync("鄭隆"); 4 Console.WriteLine(result); 5 }
如果異步方法的結果不傳遞給變量,也可以直接在參數中使用Await關鍵字。
1 private async static void CallWithAsync2() 2 { 3 Console.WriteLine(await GreetingAsync("鄭隆")); 4 }
Async修飾符只能用於返回Task和void的方法。不能用於程序的入口。
3.3延續任務
Task類的ContinueWith方法定義了任務完成后將要調用的代碼。
1 private static void CallerWithContinuationTask() 2 { 3 Task<string> t1 = GreetingAsync("鄭隆"); 4 t1.ContinueWith(t => { string result = t.Result; Console.WriteLine(result); }); 5 }
3.4使用多個異步方法
在一個異步方法中,可以調用不止一個異步方法。
1 public async static void MultipleAsyncMethods() 2 { 3 string s1 = await GreetingAsync("zhenglong1"); 4 string s2 = await GreetingAsync("zhenglong2"); 5 Console.WriteLine("Finished both methods.\n"+"Result 1:{0}\n Result 2:{1}",s1,s2); 6 }
Task.WhenAll組合器,可以讓你等待直到兩個任務都完成。
1 public async static void MultipleAsyncMethodsWithCombinators1() 2 { 3 Task<string> t1= GreetingAsync("zhenglong1"); 4 Task<string> t2 = GreetingAsync("zhenglong2"); 5 await Task.WhenAll(t1,t2); 6 Console.WriteLine("Finished both methods.\n" + "Result 1:{0}\n Result 2:{1}", t1.Result, t2.Result); 7 }
Task的WhenAll方法是在所有傳入的任務都完成了才返回Task,而WhenAny是在其中的一個任務已完成就會返回Task。
3.5轉換異步模式
並非.NET Freamwork的所有的類在4.5中都引入了新的異步方法。
1 private static Func<string, string> greetingInvoker = Greeting; 2
3 static IAsyncResult BeginGreeting(string name,AsyncCallback callback,object state) 4 { 5 return greetingInvoker.BeginInvoke(name, callback, state); 6 } 7 static string EndGreeting(IAsyncResult ar) 8 { 9 return greetingInvoker.EndInvoke(ar); 10 } 11 private async static void ConvertingAsyncPattern() 12 { 13 string s = await Task<string>.Factory.FromAsync(BeginGreeting, EndGreeting, "zhenglong", null); 14 Console.WriteLine(s); 15 }
4 錯誤處理
1 public static async Task ThrowAfter(int ms,string message) 2 { 3 await Task.Delay(ms); 4 throw new Exception(message); 5 } 6
7 public static void DontHandle() 8 { 9 try
10 { 11 ThrowAfter(200,"first"); 12 } 13 catch (Exception ex) 14 { 15 Console.WriteLine(ex.Message);17 } 18 }
以上代碼並不能捕獲到異常,應為在ThrowAfter拋出異常之前DontHandle已經執行完了。
4.1 異步方法的異常處理
異步方法異常的一個較好的處理是使用關鍵字Await,然后將其放在try中
1 public async static void HandleErrorOne() 2 { 3 try
4 { 5 await ThrowAfter(200, "first"); 6 } 7 catch (Exception ex) 8 { 9 Console.WriteLine(ex.Message); 10 } 11 }
4.2 多個異步方法的異常處理
1 public static async void StatTwoTasks() 2 { 3 try
4 { 5 await ThrowAfter(2000,"frist"); 6 await ThrowAfter(1000, "second"); 7 } 8 catch (Exception ex) 9 { 10 Console.WriteLine(ex.Message); 11 } 12 }
如上代碼並不能捕獲全部的異常,原因是因為在第一個異常拋出后程序就進入了catch。解決方法
1 public static async void StatTwoTasksParallel() 2 { 3 try
4 { 5 Task t1 = ThrowAfter(200, "zhenglong1"); 6 Task t2 = ThrowAfter(200, "zhenglong2"); 7 await Task.WhenAll(t1,t2); 8 } 9 catch (Exception ex) 10 { 11 Console.WriteLine(ex.Message); 12 } 13 }
4.3 AggregateException類
為了得到所有的異常信息,可以將Task.WhenAll返回的結果寫到一個Task中,這個任務一直等待所有的任務完成。
1 public static async void ShowAggregateException() 2 { 3 Task taskResult = null; 4 try
5 { 6 Task t1 = ThrowAfter(200, "zhenglong1"); 7 Task t2 = ThrowAfter(200, "zhenglong2"); 8 await(taskResult= Task.WhenAll(t1, t2)); 9 } 10 catch (Exception ex) 11 { 12 Console.WriteLine(ex.Message); 13 foreach (var item in taskResult.Exception.InnerExceptions) 14 { 15 Console.WriteLine(item.Message); 16 } 17 } 18 }
5取消
在某種情況下,后台任務可能運行很長的時間,取消任務就非常有用了。
5.1 開始取消任務
1 private CancellationTokenSource cts; 2 public void Cancle() 3 { 4 if (cts != null) 5 cts.Cancel(); 6 }
CancellationTokenSource 類還支持在指定時間后才取消任務,CancelAfter方法在應該取消的任務后傳入一個時間值,單位是ms.
5.2開始框架的特性任務取消
1 public async void CancelTwo() 2 { 3 cts = new CancellationTokenSource(); 4 HttpClient hc = new HttpClient(); 5 var s=await hc.GetAsync("Url", cts.Token); 6 }
5.3取消自定義任務
1 await Task.Run(()=> { },cts.Token);
現在用戶就可以取消運行時間長的任務了。
