首页 > 代码库 > 【读书笔记】C#高级编程 第十三章 异步编程
【读书笔记】C#高级编程 第十三章 异步编程
(一)异步编程的重要性
使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程。有3中不同的异步编程模式:异步模式、基于事件的异步模式和新增加的基于任务的异步模式(TAP,可利用async和await关键字来实现)。
(二)异步模式
1、C#1的APM 异步编程模型(Asynchronous Programming Model)。
2、C#2的EAP 基于事件的异步模式(Event-based Asynchronous Pattern)。
3、TAP 基于任务的异步模式(Task-based Asynchronous Pattern)。
参考:http://www.cnblogs.com/zhaopei/p/async_one.html
(三)异步编程基础
async和await关键字只是编译器功能。编译器会用Task类创建代码。
1、创建任务
1 /// <summary> 2 /// 同步方法 3 /// </summary> 4 /// <returns></returns> 5 static string SayHi(string name) 6 { 7 Thread.Sleep(3000); 8 return "你好!"+ name; 9 } 10 11 /// <summary> 12 /// 基于任务的异步模式 13 /// </summary> 14 /// <returns></returns> 15 static Task<string> SayHiAsync(string name) 16 { 17 return Task.Run<string>(()=> { 18 return SayHi(name); 19 }); 20 }
泛型版本的Task.Run<string>创建一个返回字符串的任务。
2、调用异步方法
使用await关键字需要有用async修饰符声明的方法。在await的方法没有完成前,该方法内的其他代码不会继续执行,但是调用await所在方法的线程不会被阻塞。
private async static void CallerWithAsync() { string result = await SayHiAsync("张三"); Console.WriteLine(result); }
async修饰符只能用于返回Task和void方法。
3、延续任务
Task类的ContinueWith方法定义了任务完成后就调用的代码。指派给ContinueWith方法的委托接受将已完成的任务作为参数传入,使用Result属性可以访问任务返回的结果。
private static void CallerWithContinuationTask() { Task<string> t = SayHiAsync("李四"); t.ContinueWith(_t => Console.WriteLine(_t.Result)); }
4、同步上下文
WPF应用程序设置了DispatcherSynchronizationContext属性,WindowsForm应用程序设置了WindowsFormsSynchronizationContext属性。如果调用异步方法的线程分配给了同步上下文,await完成之后将继续执行。如果不使用相同的上下文,必须调用Task类的ConfigureAwait(ContinueOnCapturedContext:false)。
5、使用多个异步方法
(1)按顺序调用异步方法
private async static void MultipleCallerWithAsync() { string result1 = await SayHiAsync("张三"); string result2 = await SayHiAsync("李四"); Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2); }
(2)使用组合器
一个组合器可以接受多个同一类型的参数,并返回同一类型的值。Task组合器接受多个Task对象作为参数,并返回一个Task。
private async static void MultipleCallerWithAsyncWithCombinators1() { Task<string> task1 = SayHiAsync("张三"); Task<string> task2 = SayHiAsync("李四"); await Task.WhenAll(task1,task2); Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2); }
Task类定义了WhenAll(全部任务完成才返回)和WhenAny(任意任务完成即返回)两个组合器。
当任务返回类型相同时,可以用数组接受返回结果。
private async static void MultipleCallerWithAsyncWithCombinators2() { Task<string> task1 = SayHiAsync("张三"); Task<string> task2 = SayHiAsync("李四"); string [] results=await Task.WhenAll(task1,task2); Console.WriteLine("完成了两次打招呼!{0} 和 {1}", results[0], results[1]); }
6、转换异步模式
当某些类没有提供基于任务的异步模式时(仅有BeginXX,EndXX),可以使用TaskFactory类定义的FromAsync方法转换为基于任务的异步模式的方法。
private static async void ConvertingAsyncPattern() { Func<string, string> method = SayHi; string result = await Task<string>.Factory.FromAsync<string>((name, callback,state) => { return method.BeginInvoke(name, callback, state); },ar=> { return method.EndInvoke(ar); },"王麻子",null); Console.WriteLine(result); }
(四)错误处理
1、异步方法的异常处理
异步方法异常的一个较好的处理方式,就是使用await关键字,将其放在try/catch语句中。
1 static async Task ThrowAfter(int ms, string message) 2 { 3 await Task.Delay(ms); 4 throw new Exception(message); 5 } 6 7 private static async void HandleOneError() 8 { 9 try 10 { 11 await ThrowAfter(2000, "first"); 12 } 13 catch (Exception ex) 14 { 15 Console.WriteLine("handled {0}", ex.Message); 16 } 17 }
2、多个异步方法的异常处理
在顺序调用两个及以上会抛出异常的方法时,不可再使用以上方法,因为当第一个异步方法抛出异常时try块里的余下方法不会再被调用。
如果需要将剩余的方法继续执行完,再对异常进行处理,可以使用Task.WhenAll方法,这样不管任务是否抛出异常,都会等到所有任务执行完。但是,也只能看见传递给WhenAll方法的第一个异常。
private static async void HandleOneError() { try { Task task1 = ThrowAfter(1000, "first"); Task task2 = ThrowAfter(2000, "second"); await Task.WhenAll(task1, task2); } catch (Exception ex) { Console.WriteLine("handled {0}", ex.Message); } }
如果需要将剩余的方法执行完,且获取所有抛出异常,可以在try块外声明任务变量,使其可以在catch块内被访问。这样可以使用IsFaulted属性检查任务的状态,当为true时,可以使用Task类的Exception.InnerException访问异常信息。
private static async void HandleOneError() { Task task1 = null; Task task2 = null; try { task1 = ThrowAfter(1000, "first"); task2 = ThrowAfter(2000, "second"); await Task.WhenAll(task1, task2); } catch (Exception ex) { if (task1 != null && task1.IsFaulted) { Console.WriteLine(task1.Exception.InnerException); } if (task2 != null && task2.IsFaulted) { Console.WriteLine(task2.Exception.InnerException); } } }
3、使用AggregateException信息
为了得到所有的异常信息,还可以将Task.WhenAll返回的结果写入一个Task变量中,然后访问Task类的Exception属性(AggregateException类型)。AggregateException定义了InnerExceptions属性,它包含了所有的异常信息。
private static async void HandleOneError() { Task task = null; try { Task task1 = ThrowAfter(1000, "first"); Task task2 = ThrowAfter(2000, "second"); await (task = Task.WhenAll(task1, task2)); } catch (Exception) { foreach (var exception in task.Exception.InnerExceptions) { Console.WriteLine(exception); } } }
(五)取消
1、取消任务
private CancellationTokenSource cts; private void OnCancel() { if (cts != null) { cts.Cancel(); //cts.CancelAfter(1000);//等待1000ms后取消 } }
2、使用框架特性取消任务
private async void OnTaskBasedAsyncPattern() { List<string> urlList = new List<string>(); urlList.Add("http://www.baidu.com"); cts = new CancellationTokenSource(); try { foreach (var url in urlList) { Random rd = new Random(); int i = rd.Next(1, 100); //1到100之间的数, if (i%2==0) { OnCancel();//当随机数为偶数时取消任务 } var client = new HttpClient(); var response = await client.GetAsync(url, cts.Token);//GetAsync方法会检查是否应该取消操作 var result =await response.Content.ReadAsStringAsync(); Console.WriteLine(result); } } catch (OperationCanceledException ex)//当任务取消时会抛出该异常 { Console.WriteLine(ex.Message); } }
3、取消自定义任务
Task类的Run方法提供了传递CancellationToken参数的重载版本。使用IsCancellationRequest属性检查令牌,用ThrowIfCancellationRequested方法触发异常。
public async void CustomerTask() { cts = new CancellationTokenSource(); var list = new List<string>(); list.Add("1"); list.Add("2"); list.Add("3"); var deal_list = new List<int>(); try { await Task.Run(() => { foreach (var item in list) { Random rd = new Random(); int i = rd.Next(1, 100); //1到100之间的数, if (i % 2 == 0) { OnCancel();//当随机数为偶数时取消任务 } if (cts.Token.IsCancellationRequested) { Console.WriteLine("处理任务异常,回滚"); deal_list.Clear(); cts.Token.ThrowIfCancellationRequested(); } deal_list.Add(Convert.ToInt32(item)); Console.WriteLine(item); } }, cts.Token); } catch (OperationCanceledException ex) { Console.WriteLine(ex.Message); } }
【读书笔记】C#高级编程 第十三章 异步编程