一、概述
async和await是C#5.0时代引入异步编程的核心关键字。通过使用异步编程,你可以避免性能瓶颈并增强程序响应能力。但是,编写异步应用程序的传统技术可能比较复杂,使异步编程难编写,调试和维护。
C#中的async和await关键字是异步编程的核心。通过这两个关键字,可以使用.NET Framework,.NET Core或Windows运行时中的资源,轻松创建异步方法。使用async关键字定义的异步方法简称为”异步方法“。
二、async异步执行机制理解
- 在C#中,async标记了一个包含异步执行的函数,通过async的函数若在主线程中直接调用,则函数一开始扔在主线程中执行。
- async标记的函数内部必须包含await标记需要异步执行的函数,若当前函数在主线程中直接调用,则await标记当前的代码在主线程中执行,await标记后的代码在其异步子线程中执行。
- async标记的函数返回值必须为void、Task、Task<TResult>类型,可以理解为async标记的函数返回的是null,即将执行的任务、带返回结果的即将执行的任务。
- async标记的函数可以继续往下调用async标记函数。
三、async
与await
应用
3.1 async
与await
简单应用
internal class Program { static void Main(string[] args) { Method1(); Method2(); Console.ReadKey(); } public static async Task Method1() { await Task.Run(() => { for (int i = 0; i < 30; i++) { Task.Delay(100); Console.WriteLine(" Method 1"); } }); } public static void Method2() { for (int i = 0; i < 25; i++) { Task.Delay(80); Console.WriteLine(" Method 2"); } } }
从上面代码运行结果可以看出,Method1和Method2相互并没有依赖关系,Method1和Method2并不是在等待对方完成。
3.2 带有返回值async
与await
应用
static void Main(string[] args) { callMethod(); Console.ReadKey(); } public static async void callMethod() { Task<int> task = Method1(); Method2(); int count = await task; Method3(count); } public static async Task<int> Method1() { int count = 0; await Task.Run(() => { for (int i = 0; i < 20; i++) { Console.WriteLine(" Method 1"); count += 1; } }); return count; } public static void Method2() { for (int i = 0; i < 25; i++) { Console.WriteLine(" Method 2"); } } public static void Method3(int count) { Console.WriteLine("Total count is " + count); }
在上面给出的代码中,Method 3需要一个参数,即Method 1的返回类型。在这里,await关键字对于等待Method 1任务的完成起着至关重要的作用。
四、async
和await
中常见问题总结
4.1 当方法用async标识时,编译器主要做了什么?
- 告诉编译器这个方法里面可能会用到await关键字来标识该方法是异步的,如此之后,编译器将会在状态机中编译此方法。接着该方法执行到await关键字时会处于挂起的状态直到该异步动作完成后才恢复继续执行方法后面的动作。
- 告诉编译器解析出方法的结果到返回类型中,比如说Task或者Task,也就是说将返回值存储到Task中,如果返回值为void那么此时应该会将可能出现的异常存储到上下文中。
4.2 当方法用async标识时,是不是所有调用者都将是异步?
当将方法用async标识时且返回值为void或者Task或者Task,此时该方法会在当前线程中一直同步执行。
用async标识方法并不会影响方法运行完成是否是同步或者异步,相反,它能够将方法划分成多块,有可能有些在异步中运行,以至于这些方法是异步完成的,而划分异步和同步方法的边界就是使用await关键字。也就是说如果在方法中未用到await关键字时则该方法就是一整块没有所谓的划分,会在同步中运行,在同步中完成。
4.3 当方法用async标识时,是否会引起方法的调用会被添加到线程池队列中或者是创建一个新的线程呢?
显然不是这样,当用async标识方法时只是显示告诉编译器在该方法中await关键字可能会被用到,当执行到await关键字开始处于挂起的状态知道异步动作执行完成才恢复(异步操作是在状态机中【有关状态机请看这里:Async和Await异步编程的原理】完成,完成后此时才会创建一个线程),这也就是为什么在方法中方法用async标识如果没有用到await关键字IDE会发出警告的原因。
到了这里我们可以得出结论:无论方法是同步还是异步都可以用async关键字来进行标识,因为用async标识只是显示表明在该方法内可能会用到await关键字使其变为异步方法,而且将该异步方法进行了明确的划分,只有用了await关键字时才是异步操作,其余一并为同步操作。
4.4 参数为什么不能使用ref和out关键字
我们知道用ref和out关键字不过是为了在方法里面改变其值,也就是是当同步完成时我们期望被ref或者out关键字修饰的值会被设置,但是它们可能在异步完成时或者之后才会被设置达不到我们预期,所以在异步方法中不能用ref和out关键字。
4.5 await关键字背后发生了什么
await关键字是这样用的
await Task.Run(() => "goyeer");
此时背后究竟发生了什么呢?我们上述也说过异步动作时在状态机中完成,当执行到这里时,编译器会自动生成代码来检测该动作是否已经完成,如果已经完成则继续同步执行await关键字后面的代码,通过判断其状态机状态若未完成则会挂起一个继续的委托为await关键字的对象直到完成为止,调用这个继续动作的委托重新进入未完成的这样一个方法。
五、总结
本节我们详细讲述了async和await关键字的使用和一些基本原理以及解释其原因,希望通过对本文的学习,对大家能够更好的去理解异步。