异步方法的结构包含有3个不同的区域:
async Task<int> CountCharactersAsync(int id,string site) { //section 1 第一个await表达式之前的部分 Console.WriteLine("Starting CounCharacters"); WebClient wc=new WebClient(); //section 2 await表达式 string result=await wc.DownloadStringTaskAsync(new Uri(site)); //section 3 后续部分 Console.WriteLine("CountCharacters Completed"); return result.Length; }
section 1:从方法开头到第一个await表达式之前的所有代码,这一部分应只包含少量且无需长时间处理的代码。
section 2:await表达式,表示将被异步执行的任务。
section 3:后续部分,await表达式之后出现的方法中的其余代码。包括其执行环节,所在线程信息,目前作用域内的变量值,以及当await表达式完成后要重新执行所需的其他信息。
一、异步方法控制流
一个异步方法的控制流,从第一个await表达式之前的代码开始,同步执行,直到遇到第一个await。这一区域实际上在第一个await表达式处结束,此时await任务还没有完成(一般没有完成)。当await任务完成时,方法将继续同步执行。如果还有其他await,就重复上述过程。(异步方法内部)
当到达await表达式时,异步方法将控制返回到调用方法(跳出异步方法至调用方法)。如果方法的返回类型为Task或Task<T>类型,就讲创建一个Task对象,表示需异步完成的任务和后续,然后将该Task返回到调用方法。
异步方法内部:
1、异步执行await表达式的空闲任务
2、当await表达式完成时,执行后续部分。后续部分本身也可能包含其他await表达式,这些表达式也将按照相同方式处理,即异步执行await表达式,然后执行后续部分。
3、当后续部分遇到return语句或到达方法末尾时,将:
3-1、如果方法返回类型为void,控制流退出
3-2、如果方法返回类型为Task,后续部分设置Task的属性并退出。如果返回类型为Task<T>,后续部分还将设置Task对象的Result属性。
同时,调用方法中的代码将继续其进程,从异步方法获取Task对象。当需要实际值时,就引用Task对象的Result属性。届时,如果异步方法设置了该属性,调用方法就能获得该值并继续。否则,将暂停并等待该属性被设置,然后再继续执行。
具体的各种控制流
1、调用方法调用异步方法——同步执行代码,没有await表达式-返回调用方法继续执行
2、调用方法调用异步方法——同步执行代码,有await表达式——await表达式任务没有完成——创建空闲任务,创建后续部分,返回到调用方法——A、返回到调用方法的部分会继续执行。B、异步方法会执行await的空闲任务——执行后续部分——设置返回的Task状态和返回值——退出
3、调用方法调用异步方法——同步执行代码,有await表达式——await表达式任务完成——同步执行代码,有await表达式.
注意:同步方法第一次遇到await时,所返回的对象类型并非await表达式的返回类型,而是同步方法头中的返回类型,它与await表达式的返回值类型没有任何关系。
例如:
private async Task<int> CountCharactersAsync(string site) { WebClient wc=new WebClient(); string result=await wc.DownloadStringTaskAsync(new Uri(site)); return result.Length; }
同步方法第一次遇到await表达式时所返回的对象其实是Task<int>,而并不是await表达式的string。Task<int>是异步方法的返回类型。而实际上return语句“返回”一个结果或到达异步方法结尾时,它并没有返回真正的某个值,而只是单纯的退出了。
二、await表达式
await表达式指定了一个异步执行的任务。其语法由await关键字和一个空闲对象/任务(Task)组成。这个任务可能是一个Task类型的对象,也可能不是。默认情况下,这个任务在当前线程异步执行。
一个空闲对象/任务即是一个awaitable类型的实例。awaitable类型是指包含GetAwaiter方法的类型,该方法没有参数,返回一个awaiter类型的对象。awaiter类型包括以下成员:
1、bool IsCompleted{get;}
2、void OnCompleted(Action);
3、void GetResult();或T GetResult();
大部分时候,不需要构建自己的awaitable,而应该使用Task类,它是awaitable类型。.Net 4.5 BCL中有很多异步方法,返回Task<T>类型对象。将这些放至await表达式中,它们会在当前线程中异步执行。
当有需要时如何编写自己的Task<T>类型方法?
最简单的办法是使用Task.Run创建一个Task,其原型如下:Task Run(Func<TReturn> func)
要将自己的方法传递给Task.Run方法,要注意的是Task.Run是在不同的线程上运行方法,需要基于该方法创建一个委托。3种不同写法:
class MyClass { public int Get10() { return 10; } public async Task DoWorkAsync() { //Func委托,最后一个参数是返回类型,可以有0-16个其他参数 Func<int> ten = Get10; int a = await Task.Run(ten); int b = await Task.Run(new Func<int>(Get10)); int c = await Task.Run(() => { return 10; }); Console.WriteLine("{0} {1} {2}", a, b, c); } } class Program { static void Main(string[] args) { Task t = (new MyClass()).DoWorkAsync(); t.Wait(); Console.ReadLine(); } }
上面的例子中,Task.Run的签名是以Func<TResult>为参数。其他的重载还包括:
Task.Run(Action action)
Task.Run(Action action,CancellationToken token)
Task<TResult>.Run(Func<TResult> function)
Task<TResult>.Run(Func<TResult> function,CancellationToken token)
Task.Run(Func<Task> function)
Task.Run(Func<Task> function,CancellationToken token)
Task<TResult>.Run(Func<Task<TResult>> function)
Task<TResult>.Run(Func<Task<TResult>> function,CancellationToken token)
当遇到特殊的方法不符合时,可以使用接收的Func委托的形式创建一个Lambda函数,这个函数只用于运行特殊方法。例如:Task.Run(()=>GetSum(4,5));