十年河东,十年河西,莫欺少年穷
学无止境,精益求精
1、简介
从 VS 2012 开始,新引入了一个简化的方法,称为异步编程。我们在 >= .NETFRM 4.5 中和 Windows 运行时中使用异步,编译器它会帮助了我们降低了曾经进行的高难度异步代码编写的工作,但逻辑结构却类似于同步代码。因此,我们仅需要进行一小部分编程的工作就可以获得异步编程的所有优点。
对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个请求时,如果我们发出请求的代码是同步实现的话,这时候我们的应用程序就会处于等待状态,直到收回一个响应信息为止,然而在这个等待的状态,对于用户不能操作任何的UI界面以及也没有任何的消息,如果我们试图去操作界面时,此时我们就会看到”应用程序为响应”的信息(在应用程序的窗口旁),相信大家在平常使用桌面软件或者访问web的时候,肯定都遇到过这样类似的情况的,对于这个,大家肯定会觉得看上去非常不舒服。引起这个原因正是因为代码的实现是同步实现的,所以在没有得到一个响应消息之前,界面就成了一个”卡死”状态了,所以这对于用户来说肯定是不可接受的
2、优势
异步编程最大的优势其实就是提供系统执行效率,毕竟一个串行执行的程序不如并行来的快。譬如:一个人要干十件事情不如十个人各干一件事情效率高。
3、关键字
C# 中的 async 和 await 关键字都是异步编程的核心。通过使用这两个关键字,我们就可以在 .NET 轻松创建异步方法。
4、返回值类型
4.1、Void
如果在触发后,你懒得管,请使用 void。
void返回类型主要用在事件处理程序中,一种称为“fire and forget”(触发并忘记)的活动的方法。除了它之外,我们都应该尽可能是用Task,作为我们异步方法的返回值。
4.2、Task
你如果只是想知道执行的状态,而不需要一个具体的返回结果时,请使用Task。
与void对比呢,Task可以使用await进行等待新线程执行完毕。而void不需要等待。
4.3、Task<TResult>
当你添加async关键字后,需要返回一个将用于后续操作的对象,请使用Task<TResult>。
主要有两种方式获取结果值,一个是使用Result属性,一个是使用await。他们的区别在于:如果你使用的是Result,它带有阻塞性,即在任务完成之前进行访问读取它,当前处于活动状态的线程都会出现阻塞的情形,一直到结果值可用。所以,在绝大多数情况下,除非你有绝对的理由告诉自己,否则都应该使用await,而不是属性Result来读取结果值。
5、范例
再进行范例之前,先写一个错误的异步方法,如下:

public static async Task SyncExec_3() { Proc(); } public static void Proc() { for (int i = 0; i < 1000; i++) { Console.WriteLine(i); } }
由上图截图可以,在异步方法内,需要使用await关键字,否则方法会同步执行。
不是说你把一个方法标记成async这个方法就成了异步调用的方法了。async这个关键词其实反而是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下文而已。
如下两个方法其实是一样的

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; }
那么,既然async是可以省略的,那么await可以省略吗?答案是不可以,否则你的方法会被编译警告,会成为一个同步方法。
其实真正重要的是await,有没有async反而确实不重要。既然微软提供了这样的语法糖,所以建议大家在写异步方法是加上async。
下面我们通过实例来说明异步编程,如下:
5.1、返回值为Task的程序具体返回了什么?

public static async Task SyncExec_2() { await Task.Run(() => { Proc(); }); }
通过调试,快速监视,得到如下消息:
其实返回值为Task的方法中什么也没返回,但是我们确定接收到他的返回值,这点似乎是个矛盾点。根据VS快速监视截图,我们发现我们接收的东西是一个上下文线程。
5.2、异步执行的顺序
核心代码:

using System; using System.Diagnostics; using System.Threading.Tasks; namespace ConsoleCore { class Program { static void Main(string[] args) { var Result = ComplexWorkFlow(); Console.WriteLine("执行结束"); Console.Read(); } /// <summary> /// 加法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Add(int num1, int num2) { return num1+ num2; } /// <summary> /// 减法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Sub(int num1,int num2) { return num1 - num2; } /// <summary> /// 乘法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Mul(int num1,int num2) { return num1 * num2; } /// <summary> /// 除法 /// </summary> /// <param name="num1"></param> /// <param name="num2"></param> /// <returns></returns> public static int GetNum_Cal(int num1, int num2) { return num1 / num2; } /// <summary> /// 10 /// </summary> /// <returns></returns> public static async Task<int> DoTask1() { var result = 0; await Task.Run(() => { result = GetNum_Add(5, 5); }); Console.WriteLine("任务 DoTask1 执行完毕,结果为:" + result); return result; } /// <summary> /// 90 /// </summary> /// <returns></returns> public static async Task<int> DoTask2() { var result = 0; await Task.Run(() => { result = GetNum_Sub(100,10); }); Console.WriteLine("任务 DoTask2 执行完毕,结果为:" + result); return result; } /// <summary> /// 100 /// </summary> /// <param name="A"></param> /// <returns></returns> public static async Task<int> DoTask3UseResultOfTask1(int A) { var result = 0; await Task.Run(() => { result = GetNum_Mul(A, A); }); Console.WriteLine("任务 DoTask3UseResultOfTask1 执行完毕,结果为:" + result); return result; } /// <summary> /// 45 /// </summary> /// <param name="A"></param> /// <returns></returns> public static async Task<int> DoTask4UseResultOfTask2(int A) { var result = 0; await Task.Run(() => { result = GetNum_Cal(A, 2); }); Console.WriteLine("任务 DoTask4UseResultOfTask2 执行完毕,结果为:" + result); return result; } /// <summary> /// 两者平方和 /// </summary> /// <param name="A"></param> /// <param name="B"></param> /// <returns></returns> public static async Task<int> DoTask5(int A, int B) { var result = 0; await Task.Run(() => { result = A * A + B * B; }); Console.WriteLine("最终的结果为:" + result); return result; } public static 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); } } }

发现没有:执行结束反而先执行完毕了,呵呵呵,意不意外?
继续执行一次:
这次执行结束换到了第二行,呵呵,意不意外?
再执行一次:
额,(⊙o⊙)…又跑到第三行了,不执行了,大概说下吧。
task1和task2并行,task3和task4并行,但是task1肯定会在task3之后,Task2肯定会在task4之后,task3 和 task4 肯定会在Task5之后,至于输出的执行结束这段话,可能出现在任何位置。哈哈。就这么任性。
上述探讨了异步方法,但:
首先要知道async await解决了什么问题,不要为了异步而异步,针对高密集的cpu计算异步没太大意义,甚至可能有性能损耗。
其次说async await的实现,就以你的代码为例,如果没有async await的话代码执行步骤就不说了,在有async await后就不一样,一旦调用一个async方法,就是告知,这里我可能需要点时间来处理,你先继续往后走吧(比如io操作),这块执行线程就会继续往后跑而不再关心async方法的返回直到看到对应的await后,就停下来等着await对应的task执行完(你async await的代码在编译后会变成一个状态机,这个你可以看下你这段代码在il中的实现),执行完后就会从对应的task展开(unwarp)拿到原始结果(比如你代码中几个await的地方),这里额外就可以回答你第Task和async await的差异,async await的表现是基于Task,但显式的Task会根据TaskScheduler启动线程,而async await不会额外新起线程,async await会从当前可用线程中找空闲的线程来执行,由于所有线程都没闲着(没有所谓的等待,特别是耗时的io等待),因此服务的吞吐量会高很多(适用于高io场景)
其实上面也解释了多线程和async await的差异了,多线程不等同于异步,你拿TaskFactory或者ThreadPool搞一堆线程,它们都做着同步的工作还是会在执行的时候阻塞,线程大量的时间就这样白白浪费在了等待响应上了。
参考文档:https://www.zhihu.com/question/58922017