勇哥注:
这个提问者对异步的误解决,让勇哥想到同事持相似观点,认为异步只不过是用线程池在工作罢了。
因此把知乎这篇问答摘下来,加深对异步的了解。
提问者:
---------------------------------------
用async修饰的方法本身就是期望它为一个异步方法,可是为什么该方法内必须要有await?
举个例子,有一个方法 double Factorial(int i),作用是计算i的阶乘,非异步的方法是
public double Factorial(int i)
{
double r=1.0;
do{r*=i}while(--i);
return r;
}
调用: var r = Factorial(i):
现在改成异步时,public async Task<double> Factorial(i)里必须要用Task并且要await task,为何不能用以下形式:
public async double Factorial(int i)
{
double r=1.0;
do{r*=i}while(--i);
return r;
}
调用时 var r = await Factorial(i);
因为方法的声明async和用await调用已经很明确了异步,可是c#的async/await为什么设计成在Factorial里再“多此一举”(可能是有必要的,特打上引号)用Task, await Task,这是合理和必要的吗?如果是的,为什么呢?
换一种表述,既然用async声明一个内部没有await的方法是脱裤子放屁,那么为什么不增强async的作用而要保留这么一个脱裤子放屁的使用方式。例如
async double Factorial,编译时编译成2个方法,一个是正常的double Factorial,另一个是供异步调用的async_Factorial,当不使用await调用时选择调用Factorial,await调用时调用async_Factorial,而这个async_Factorial里面干的事情就是将Factorial塞进Task里然后返回await task。
回答者(1)
------------------------------------------------
不是一般的混乱……
首先一个被标记为async的方法,可以没有await调用,只不过会有编译警告。
这是很显然的,不是说你把一个方法标记成async这个方法就成了异步调用的方法了。async这个关键词其实反而是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下文而已。
所以,一个async的方法里面没有await的调用,那等于是脱了裤子放屁,本质上只是把return xxx改成了retrurn Task.FromResult( xxx )而已,没有任何变化。如果一个方法加上了async他就自动成为了异步的调用,说明你连最根本的异步是什么都没搞清楚。你所理解的那种所谓的异步,直接用Task.Run就可以了。
回答者(2)
------------------------------------------------
题主在概念上确实混淆的不行,但是确实async/await这个东西继承了我软从VB6开始的光荣传统(易学难精)。工作中确实没有碰到几个真正搞的明白的同事(轮子哥,I大这种论外)。
首先,题主需要搞明白一个概念,就是async不是函数声明的一部分。从调用者的角度来看,不存在async这个东西。
async是一个专门给编译器的提示,意思是该函数的实现可能会出现await。至于为啥要有这个提示,而不是编译器发现函数实现里有await的时候就自动加上async标志,这是定义语言标准时的选择,C#(这个feature)的作者也许认为这样写让作者更明确的意识到自己在实现一个包含异步调用的函数。(我瞎猜的)
勇哥注:其实关于为什么要设置async关键字,微软的开发团队已经做了说明,这个不用猜想。请查阅:http://www.skcircle.com/?id=1841
下面举两个例子
Task<int> DelayAndCalculate1(int a, int b){ return Task.Delay(1000).ContinueWith(t => a + b);}async Task<int> DelayAndCalculate2(int a, int b){ await Task.Delay(1000); return a + b;}
这两个函数(不算函数名的不同),在函数声明上是完全没有区别的。只是其中一个在实现中使用了await,所以C#语法要求我们必须在标示async。
从调用者的角度来看,这两个函数完全一致(而且行为也一致),都可以使用await关键词unwrap Task类型的返回值(其实这里有一个叫做GetAwaiter的约定,这里不展开说)。
另一个佐证就是interface的定义中不能写async,因为如上所述,async不是函数声明,而其实编译函数实现的提示。

最后回到题主的问题为啥有了async还要写await,其实真正重要的是await(和其他异步的实现,如例子中的DelayAndCalculate1),有没有async反而确实不重要。
那么可不可以如题主所说的设计一个语法糖,不需要写Task<int>而写async int呢?答案是当然可以这么设计,但这就只是单纯的让编译器自动把async int翻译成Task<int>而已,C#的作者单纯没有这么设计而已(因为这会使得函数的返回值和声明时不同,我个人也觉得这会导致更大的confusion)。因为一个返回Task<int>的函数,不只可以用来await,还有很多别的玩法,语言应该给予开发人员解开和不解开的自由。最简单的例子:
async Task<int> ComplexWorkFlow(){ Task<int> task1 = DoTask1(); Task<int> task2 = DoTask2(); Task<int> task3 = DoTask3UseResultOfTask1(await task1); Task<int> task4 = DoTask4UseResultOfTask2(await task2); return await DoTask5(await task3, await task4);}
我用短短几行实现了一个相对复杂的工作流,task直接的dependency很明确的表达在代码里,并且task1和task2可以并行执行,task3和task4可以并行执行(事实上更好的写法可以让task1->task3完全并行与task2->task4)。核心思路就是只有当某个task的执行结果需要被使用的时候才解开这个task的值(等它执行完毕)。
想象一下这个例子里面如果按照你的语法糖来实现,会有多奇怪。
勇哥注: 回答者(1)确实说的够清楚了。问者所理解的异步,仅仅是利用线程池的后台线程罢了。这样完全没懂得异步的好处在哪里。

