异步编程
1)异步编程的重要性
在C#5.0中提供了关键字:async和await
使用异步编程后台运行方法调用,程序的运行过程中就不会一直处于等待中。便于用户继续操作.
异步编程有3种模式:异步模式、基于事件的模式、基于任务的模式。
基于任务的模式就使用了关键字。
2)异步模式
public delegate int AddHandler(int a,int b);public class 加法类{public static int Add(int a, int b) { Console.WriteLine("开始计算:" + a + "+" + b); Thread.Sleep(1000); Console.WriteLine("计算完成!"); return a + b; }}
2.1)同步调用
先调用,待处理
static void Main(){ Console.WriteLine("===== 同步调用 SyncInvokeTest ====="); AddHandler handler = new AddHandler(加法类.Add); int result = handler.Invoke(1, 2); Console.WriteLine("继续做别的事情。。。"); Console.WriteLine(result); Console.ReadKey();}
2.2)异步模式APM(Asynchronous Programming Model)
先处理,待调用
异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
异步模式定义了开始调用方法和结束调用方法BeginInvoke和EndInvoke
开始调用方法接受同步方法的所有输入参数 结束调用方法使用同步的所有输出参数,按照同步方法返回类型返回结果。 开始调用方法中包含了一个参数。该参数用于接受异步方法执行完成后调用的委托。 开始调用方法返回异步结果接口IAsyncResult,用于验证调用是否已经完成,并且一直等到方法的执行结束。
static void Main() { Console.WriteLine("===== 异步调用 AsyncInvokeTest ====="); AddHandler handler = new AddHandler(加法类.Add); //IAsyncResult: 异步操作接口(interface) //BeginInvoke: 委托(delegate)的一个异步方法的开始 IAsyncResult result = handler.BeginInvoke(1, 2, null, null); Console.WriteLine("继续做别的事情。。。"); //异步操作返回 Console.WriteLine(handler.EndInvoke(result)); Console.ReadKey();}
异步模式实现了先进行所有事务的处理,最后等待处理的结果一起反映给客户端。
2.3)基于事件的异步模式EAP(Event-based Asynchronous Pattern)
基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式
该异步模式具有以下优点:
· “在后台”执行耗时任务(例如下载和操作),但不会中断您的应用程序。
· 同时执行多个操作,每个操作完成时都会接到通知(在通知中可以区分是完成了哪个操作)。
· 等待资源变得可用,但不会停止(“挂起”)您的应用程序。
· 使用熟悉的事件和委托模型与挂起的异步操作通信。
基于事件的异步模式需要以下三个类型的支持
AsyncOperation:
提供了对异步操作的生存期进行跟踪的功能,包括操作进度通知和操作完成通知,并确保在正确的线程或上下文中调用客户端的事件处理程序。
AsyncOperationManager:
为AsyncOperation对象的创建提供了便捷方式,通过CreateOperation方法可以创建多个AsyncOperation实例,实现对多个异步操作进行跟踪
WindowsFormsSynchronizationContext:
该类继承自SynchronizationContext类型,该类型是基于事件异步模式通信的核心,该类型解决了“保证SendOrPostCallback委托在UI线程上执行”的问题
static void Main(string[] args) { object userState = "check"; System.Collections.Specialized.HybridDictionary userStateToLifetime = new System.Collections.Specialized.HybridDictionary(); AsyncOperation asyncOp = System.ComponentModel.AsyncOperationManager.CreateOperation(userState); lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(userState)) { throw new ArgumentException("同一时间不同IP操作过多,出现了并发", "操作状态"); } userStateToLifetime[userState] = asyncOp; } Action
基于事件的异步模式与普通的异步模式不同点的是,基于事件的异步可以在不同异步方法中串联,可以通过对象状态通知跟踪挂起的操作,取消挂起的操作,接收进度更新和增量结果。
因此我们需要对多个异步方法进行互通消息时,使用事件的异步模式,一般的使用BeginInvoke和EndInvoke实现好了.
一般基于事件的异步方法以Async为方法签名后缀
2.4)基于任务的异步模式TAP(Task-based Asynchronous Pattern)
我们如果看到有类的方法名后缀以TaskAsync结尾,那就是基于任务的异步模式了。
基于任务的异步模式同样也支持异步操作的取消和进度的报告的功能。
基于任务的异步模式使用Task类实现。还提供了关键字async和await
async异步执行
await等待任务返回
使用关键字async和await实现同步调用:
static void Main(string[] args) { Console.WriteLine("主线程测试开始.."); AsyncMethod(); Thread.Sleep(1000); Console.WriteLine("主线程测试结束.."); Console.ReadLine(); } static async void AsyncMethod() { Console.WriteLine("开始异步代码"); var result = await MyMethod(); Console.WriteLine("异步代码执行完毕"); } static async Task MyMethod() { for (int i = 0; i < 5; i++) { Console.WriteLine("异步执行" + i.ToString() + ".."); await Task.Delay(1000); //模拟耗时操作 } return 0; }
从代码中可以看出.await和async关键字带来的新优势.
对于运行结果来看,主线程正常操作,await将延迟操作持续响应.持续响应完毕后输出异步代码执行完毕。
我们需要了解的一点是:
使用await
的非同步方法必须由 关键字修改,await
关键字声明的任务方法必须具有返回值,并且返回值是Task类型的,方法中如果使用了await关键字则,方法必须声明为异步的,必须用async进行声明. 使用async可以将方法、lamdba表达式、委托声明为异步的.
3)异步编程基础
下面我们将使用Task类来创建异步编程.
3.1)创建任务
基于任务的异步模式指定,在异步方法名后加上Async后缀,并返回一个任务,返回的是一个Task类型,看下面示例:
// 同步方法 static string Greeting(string name){ Console.WriteLine("开始Greeting"); Thread.Sleep(3000); return string.Format("Hello, {0}", name);}
// 异步方法 static TaskGreetingAsync(string name){ return Task.Run (() => { Console.WriteLine("开始GreetingAsync"); return Greeting(name); });}
3.2)调用异步方法
使用await关键字调用返回任务的异步方法,使用await关键字需要有async修饰符声明的方法
async修饰符只能用于返回Task或void方法,await只能用于返回task的方法// 异步方法调用 private async static void CallerWithAsync(){ Console.WriteLine("started CallerWithAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); string 结果 = await GreetingAsync("Stephanie"); Console.WriteLine(结果); Console.WriteLine("finished GreetingAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);}
3.3)延续任务
Task类的ContinueWith方法定义了任务完成后调用的代码,ContinueWith方法的委托参数接收已完成的任务作为参数传入,使用Result属性访问任务返回结果
// 延续任务ContinueWith private static void CallerWithContinuationTask() { Console.WriteLine("开始 CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); var t1 = GreetingAsync("Stephanie"); t1.ContinueWith(t => { string 结果 = t.Result; Console.WriteLine(结果); Console.WriteLine("finished CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId); }); }
3.4)同步上下文
使用async和await关键字,当await完成之后,不需要处理,就能访问ui线程,默认生成的代码会把线程转换到同步上下文的线程中
3.5)使用多个异步方法
一个异步方法中可以调用一个或多个异步方法.
1.使用await按顺序调用异步方法
// 顺序调用异步方法 private async static void MultipleAsyncMthods() { string s1 = await GreetingAsync("Stephanie"); string s2 = await GreetingAsync("Matthias"); Console.WriteLine("运行 所有方法.\n 结果 1: {0}\n 结果 2: {1}", s1, s2); } // GreetingAsync异步方法第2次完全独立第一次调用的结果
2.使用组合器
如果异步方法不依赖于其他方法,每个异步方法都不使用await,而是把每个异步方法的返回结果赋值给Task变量,运行会更快
一个组合器可以接受多个同一类型的参数,并返回同一类型的值。多个同一类型的参数被组合成一个参数来传递。Task组合器接受多个Task对象作为参数,返回一个Task
private async static void MultipleAsyncMethodsWithCombinators1(){ Taskt1 = GreetingAsync("Stephanie"); Task t2 = GreetingAsync("Matthias"); await Task.WhenAll(t1, t2); Console.WriteLine("运行 所有方法.\n 结果 1: {0}\n 结果 2: {1}", t1.Result, t2.Result);}
3.6)转换异步模式
使用Task类.Factory.FromAsync方法可以将同步调用转换为异步调用
第一个参数为开始调用BeginInvoke,第二个参数为结束调用EndInvoke,第三个参数为开始调用的委托参数值
private static async void ConvertingAsyncPattern() { string s = await Task.Factory.FromAsync(BeginGreeting, EndGreeting, "Angela", null); Console.Write(s); }
4)错误处理
使用Task.Delay(1000)设置任务延迟时间
4.1)异步方法的异常处理
异步方法的异常,使用await关键字进行等待,并且包含在try-catch代码块中
4.2)多个异步方法的异常处理
多个异步方法的异常,使用组合器,处理异常
private async static void StartTwoTasksParallel() { Task t1 = null; try { t1 = ThrowAfter(2000, "first"); Task t2 = ThrowAfter(1000, "second"); await Task.WhenAll(t1, t2); } catch (Exception ex) { // just display the exception information of the first task that is awaited within WhenAll Console.WriteLine("handled {0}", ex.Message); } }
4.3)使用AggregateException信息
通过定义的任务结果,遍历结果的Exception.InnerExceptions获取每一个任务的异常信息
private static async void ShowAggregatedException() { Task taskResult = null; try { Task t1 = ThrowAfter(2000, "first"); Task t2 = ThrowAfter(1000, "second"); await (taskResult = Task.WhenAll(t1, t2)); } catch (Exception ex) { // just display the exception information of the first task that is awaited within WhenAll Console.WriteLine("handled {0}", ex.Message); foreach (var ex1 in taskResult.Exception.InnerExceptions) { Console.WriteLine("inner exception {0} from task {1}", ex1.Message, ex1.Source); } } }
5)取消
5.1)开始取消任务
在System.Threading命名空间中定义了CancellationTokenSource类用于取消发送请求,使用Cancel方法进行取消或者使用CancelAfter设置指定时间后取消
5.2)使用框架特性取消任务
通过CancellationTokenSource类对象cts.Token属性可以判断任务是否取消,通过指定Token属性进行取消任务
5.3)取消自定义任务
通过CancellationTokenSource类对象cts.Token.ThrowIfCancellationRequested();
await Task.Run(()=>{ var images=req.Parse(resp); foreach(var image in images) { cts.Token.ThrowIfCancellatioRequested(); searchInfo.List.Add(image); }},cts.Token);
异步的三种模式:
1. 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续。
2. 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情。
3. 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成。在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结构。
6)学习检验
题目:
1) 为什么要使用异步
2) 异步解决了软件应用中的哪些问题
3) Async和await分别是什么
4) 什么是并行编程
5) 委托包含的2种异步方法是什么
6) 基于事件的异步和APM异步调用的区别
7) 同步与异步的区别
8) 组合器是什么
9) CancellationTokenSource类是用来做什么的
10) Task类任务中使用什么方法可以延续任务调用