世界很单纯,复杂的是人呐~
async和await也是如此。语法和含义很简单,程序员想多了,这东西就显得特别复杂。
async
含义:异步。它修饰的方法里,通常有其他异步操作。普通操作(通常是前半截)执行完了,控制权就返回“调用它的方法(主方法)”了(通常带回一个Task)。当异步操作(通常是后半截)执行完之后,主方法可以从它返回的Task里拿需要的东西(也可以不拿)。前后半截的分界线,就是await。
ps:这东西一般出现在方法头部,但不属于方法签名的一部分。也就是说,不能出现在接口和抽象类的方法说明中,可以出现在实现的方法头。
await
含义:在可等待方法中,await就是前后两段代码的分界线。它前面的代码被编译成一个方法,它和它后面的代码被编译成另一个方法。第一个方法,通常由主线程执行;第二个方法,通常由新建的线程(一般在异步嵌套调用的最里层新建)执行。它等待的,是一个异步操作的结果。
注意:子方法运行到await的时候,主方法继续执行,而子方法等待它等待的结果,然后执行它后面的语句。
用途
通常,设计一个异步(可等待)方法,就是为了方便别人调用它,它执行异步操作的时候,别人还能干自己的事。
问题来了:怎么用?
不止是语法问题,还要跟我们平时的编程习惯……最好是同步编程习惯贴合,才舒适。
先看一段耗时操作:
先花1秒,计算n!
static int jc(int x) { int y = 1; Thread.Sleep(1000); for (int i = 2; i <=x; i++) { y *= i; } return y; }
针对耗时操作,肯定会想到异步调用。常规异步调用:
var t=Task.Run(() => jc(5));//求5!,开始算吧 Console.WriteLine("我忙我的"); Console.WriteLine($"5!={t.Result}");
既然耗时操作,一般都是异步调用。那我就给它包装个异步方法,以期更好调用:
static async Task<int> jc1(int x) { var t = Task.Run(() => jc(x)); Console.WriteLine($"异步计算{x}!已经开始了,主程序继续..."); await t; Console.WriteLine($"{x}!算完了"); return t.Result; //return await Task.Run(() => jc(x));//极简写法 }
遇到await,主程序得到一个“必有int返回值”的承诺,就继续执行。子程序当Task执行完毕的时候,await向下继续执行。
调用方法就可以写成:
var t = jc1(5);//异步求5!,开始算吧,我继续。。。 Console.WriteLine("我忙我的"); Console.WriteLine($"5!={t.Result}");
第一行开始计算,第三行索要结果(谁让你承诺必有结果呢)。少了task,更符合人类习惯了。
第三行“t.Result”那里如果是“await t”,这个主方法也就是异步方法了。
总结:
一、无论是自己写的,还是微软写的异步方法(下面叫做S)里,必然有一个多线程的所谓“耗时操作”。
二、调用时,可以
- await S();阻塞式异步调用;
- S().wait();阻塞式同步调用;
- var t=S();/*并行操作*/;await t;并行异步调用;
- var t=S();/*并行操作*/;t.wait();并行同步调用。
所谓异步/同步调用,即所在的方法是异步/同步方法。
三、不管套多少层的异步,一般也就两个线程。