C#5.0最重要的改进是提供了更强大的异步编程,C#5.0仅增加两个关键字Async和Await,使用异步编程,方法调用是后台运行(通常在线程和任务的帮助下),并且不会阻塞调用线程。
从.net1.0开始就提供了异步特性,而且.NET Framework的许多类都实现了一个或多个异步模式(委托也实现了异步模式)。因为在WIndows From和WPF中用异步模式更新界面比较复杂,所以在2.0中提供了基于事件的异步模式。现在在4.5中推出了另外一种新的方式来实现异步编程:基于任务的异步模式。这种模式是基于4.0中新增的Task类型,和一个利用Async和Await关键字的编译器功能。
1 WebClient wc = new WebClient(); 2 string s= wc.DownloadString( " 你的URL " );
运行该代码,程序一直在等待DownloadString的完成,在这种情况下使用异步非常有必要的。
使用异步模式是进行异步调用的方式之一,实现异步模式定义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();
异步模式的优势是使用委托功能就能实现异步编程,不用改变程序的行为,也不会阻塞界面的操作。
基于事件的异步模式定义了一个带有"Aysnc"后缀的方法。异步方法完成时不是定义被调用的委托,而是定义事件。
1 WebClient client = new WebClient(); 2 client.DownloadStringCompleted += (sender, e1) => { }; 3 client.DownloadStringAsync(new Uri("你的URL"));
基于事件的异步方式的优势是易用。
1 public async void Fun() 2 { 3 WebClient client = new WebClient(); 4 string s = await client.DownloadStringTaskAsync("你的URL"); 5 }
现在代码简单多了,并且没有阻塞。
async和await关键字只是编译器功能,编译器会用Task创建代码。
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 }
可以使用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的方法。不能用于程序的入口。
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 }
在一个异步方法中,可以调用不止一个异步方法。
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。
并非.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已经执行完了。
异步方法异常的一个较好的处理是使用关键字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 }
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 }
为了得到所有的异常信息,可以将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 }
在某种情况下,后台任务可能运行很长的时间,取消任务就非常有用了。
1 private CancellationTokenSource cts; 2 public void Cancle() 3 { 4 if (cts != null) 5 cts.Cancel(); 6 }
CancellationTokenSource 类还支持在指定时间后才取消任务,CancelAfter方法在应该取消的任务后传入一个时间值,单位是ms.
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 }
1 await Task.Run(()=> { },cts.Token);
现在用户就可以取消运行时间长的任务了。