async/Await使用和原理
await/async是.NetFramework4.5出现的,是语法糖,由编译器提供的功能!
await/async 是C#保留关键字,通常是成对出现,一般的建议是:要么不用,要么用到底
- async修饰方法,可以单独出现,但是没有任何意义,而且有警告
- await在方法体,只能出现在task/async方法前面,只有await会报错
下面来使用代码来剖析async和await的用法。
一:只有一个async没有await
1 /// <summary> 2 /// 只有async没有await,会有个warn 3 /// 跟普通方法没有区别 4 /// </summary> 5 private static async void NoReturnNoAwait() 6 { 7 //主线程执行 8 Console.WriteLine($"NoReturnNoAwait Sleep before Task,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 9 Task task = Task.Run(() =>//启动新线程完成任务 10 { 11 Console.WriteLine($"NoReturnNoAwait Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 12 Thread.Sleep(3000); 13 Console.WriteLine($"NoReturnNoAwait Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 14 }); 15 16 //主线程执行 17 Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 18 }
调用如下:
1 private async static Task Test() 2 { 3 Console.WriteLine($"start 当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 4 { 5 NoReturnNoAwait(); 6 } 7 Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 8 Console.Read(); 9 }
运行结果如下:
如果没有使用async,这就相当于一个普通的方法,另外注意的是方法的返回值使用Task接收,可以不用直接return
二:方法有async也有await
1 /// <summary> 2 /// async/await 3 /// 不能单独await 4 /// await 只能放在task前面 5 /// 不推荐void返回值,使用Task来代替 6 /// Task和Task<T>能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行 7 /// </summary> 8 private static async Task NoReturn() 9 { 10 //主线程执行 11 Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 12 TaskFactory taskFactory = new TaskFactory(); 13 Task task = taskFactory.StartNew(() => 14 { 15 Console.WriteLine($"NoReturn Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 16 Thread.Sleep(6000); 17 Console.WriteLine($"NoReturn Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 18 }); 19 20 await task;//主线程到这里就返回了,执行主线程任务 21 Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 22 23 //注意上面 24 // await task; 25 // Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 26 //等同于下面的代码 27 //task.ContinueWith(t => 28 //{ 29 // Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); 30 //}); 31 }
调用如下:
1 private async static Task Test() 2 { 3 Console.WriteLine($"start 当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 4 { 5 NoReturn(); 6 for (int i = 0; i < 5; i++) 7 { 8 Thread.Sleep(3000); 9 Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")} i={i}"); 10 } 11 } 12 Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 13 Console.Read(); 14 }
运行结果如下:
从上面结果可以看出:
1:主线程调用async/await方法,主线程遇到await返回执行后续动作,
2:await后面的代码会等着task任务的完成后再继续执行,其实就像把await后面的代码包装成一个continue的回调动作
3: 然后这个回调动作可能是Task线程,也可能是新的子线程,也可能是主线程
三:有返回值的task,如果要得到返回值,必须要t.Result
1 /// <summary> 2 /// 带返回值的Task 3 /// 要使用返回值就一定要等子线程计算完毕 4 /// </summary> 5 /// <returns>async 就只返回long</returns> 6 private static async Task<long> SumAsync() 7 { 8 Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 9 long result = 0; 10 11 await Task.Run(() => 12 { 13 for (int k = 0; k <3; k++) 14 { 15 Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 16 Thread.Sleep(1000); 17 } 18 for (long i = 0; i < 999999999; i++) 19 { 20 result += i; 21 } 22 }); 23 24 Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 25 await Task.Run(() => 26 { 27 for (int k = 0; k <3; k++) 28 { 29 Console.WriteLine($"SumAsync11111 {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 30 Thread.Sleep(1000); 31 } 32 for (long i = 0; i < 999999999; i++) 33 { 34 result += i; 35 } 36 }); 37 Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 38 39 return result; 40 }
调用如下:
1 private async static Task Test() 2 { 3 Console.WriteLine($"start 当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 4 { 5 Task<long> t = SumAsync(); 6 Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 7 long result = t.Result;//访问result 主线程等待Task的完成 等价于t.Wait(); 8 9 10 Console.WriteLine($"result=={result}"); 11 } 12 Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); 13 Console.Read(); 14 }
得到的结果如下:
通过上面可以得到:如果使用t.Result 或者t.Wait()则相当于堵塞主线程,也就是等待子线程完成之后,主线程才能进行下面的操作,而await 则是不堵塞主线程
四:下面通过代码和反编译看一下async的底层是怎么实现的
1 public class AwaitAsyncILSpy 2 { 3 public static void Show() 4 { 5 Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 6 Async(); 7 Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 8 } 9 private static async void Async() 10 { 11 Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 12 await Task.Run(() => 13 { 14 Thread.Sleep(500); 15 Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 16 }); 17 Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 18 } 19 }
运行得到的结果根据上面的分析肯定是:15234
我们使用ILSPy来反编译dll,得到下图:
我们通过上图发现方法中加了async则会在反编译的时候增加的AsyncStateMachine状态(实现了IAsyncStateMachine接口),它实现的原理是:
初始化状态-1--执行就修改状态0--再执行就修改状态-1---执行就修改状态0---如果出现其他状态(-2)就结束了,就比如红绿灯一样,一样无限制的循环红灯-绿灯-红灯-绿灯
底层实现如下图: