async/Await使用和原理

我是研究僧i 提交于 2020-04-24 03:01:41

await/async是.NetFramework4.5出现的,是语法糖,由编译器提供的功能!

await/async 是C#保留关键字,通常是成对出现,一般的建议是:要么不用,要么用到底

  1. async修饰方法,可以单独出现,但是没有任何意义,而且有警告
  2. 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  }
View Code

调用如下:

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 }
View Code

运行结果如下:

如果没有使用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  }
View Code

调用如下:

 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 }
View Code

运行结果如下:

从上面结果可以看出:

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 }
View Code

调用如下:

 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 }
View Code

得到的结果如下:

通过上面可以得到:如果使用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     }
View Code

运行得到的结果根据上面的分析肯定是:15234

我们使用ILSPy来反编译dll,得到下图:

我们通过上图发现方法中加了async则会在反编译的时候增加的AsyncStateMachine状态(实现了IAsyncStateMachine接口),它实现的原理是:

初始化状态-1--执行就修改状态0--再执行就修改状态-1---执行就修改状态0---如果出现其他状态(-2)就结束了,就比如红绿灯一样,一样无限制的循环红灯-绿灯-红灯-绿灯

底层实现如下图:

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!