• async/Await使用和原理


    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)就结束了,就比如红绿灯一样,一样无限制的循环红灯-绿灯-红灯-绿灯

    底层实现如下图:

  • 相关阅读:
    我是怎么做App token认证的
    APP和服务端-架构设计(二)
    APP和服务端-架构设计(一)
    拦截和跟踪HTTP请求的主要方法及实现
    权限控制方案之——基于URL拦截
    你真的会用Retrofit2吗?Retrofit2完全教程
    科学的解决Http Token拦截器TokenInterceptor实现
    谈谈敏捷开发(转)
    Modbus TCP 示例报文
    Modbus 通信协议详解
  • 原文地址:https://www.cnblogs.com/loverwangshan/p/10524464.html
Copyright © 2020-2023  润新知