async/await的基本说明

  1. 用来执行异步方法的语法糖。
  2. 用async来修饰一个方法,表明这个方法是异步的
  3. 声明的async方法的返回类型必须为:void或Task或Task,哪怕不返回,最好也是返回一个Task类型
  4. 方法内部必须含有await修饰的方法,如果方法内部没有await关键字修饰的表达式,哪怕函数被async修饰也只能算作同步方法,执行的时候也是同步执行的。
  5. 被await修饰的只能是Task或者Task类型,通常情况下是一个返回类型是Task/Task的方法,当然也可以修饰一个Task/Task变量,await只能出现在已经用async关键字修饰的异步方法中。

在很多包里面都能看到异步的方法,方法的后缀Async结尾,比如网络请求的类,EF,读取文件等。
原理如下:
把耗费时间的方法装到Task里面,执行这个Task就相当于新开了一个线程去并行执行。

关于使用了await感觉和同步一样

有个疑问,就是写了await就要等待对应的async方法执行完毕,然后在进行下面的步骤。那这和同步有什么区别? :::success

  1. 区别在于当前线程不被阻塞。这点在控制台不明显,但是一旦到了桌面应用程序就明显了。比如下面的wpf例子2代码,如果写入文本,网络请求,读取文本都用同步的方式,因为需要耗费一定的时间,主线程会被卡死。页面动不了。但是用了异步方法,虽然await等待方法完成,得到返回结果。但是主线程不会卡死。举个例子,桌面软件,点击按钮后进行某复杂耗时的计算,最后要把这个结果显示到页面上。如果用同步的话,在计算的时候,整个软件出现卡死状态。一般情况都会开一个Task进行计算。计算完成后用await得到Task的结果,展示到页面上,这个过程虽然也耗时,但是软件没有出现被卡死的状态。
  2. async/await底层是状态机模式
  3. await调用等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出一个线程执行后续的代码。比如下面控制台例子1代码中。最终结果如下。最开始的线程,在执行异步方法之后,有可能接着执行的就是另外的线程了。举个例子:线程是服务员,代码是客户,同步代码就是一个服务员一直等着客户点菜。异步就是服务员给菜单给客户,客户自己点菜,这时候服务员就去做其他的事情了,比如接待新客户。等客户点菜完成的时候,有服务员继续为客户服务,但是这个服务员不一定是刚才的服务员。看餐厅的人员调度。

总的来说,点菜这个流程还是要花费时间的。不可能你还在点菜,厨师就并行的做菜。不过异步的点菜,对服务员的调度更加灵活。
关于wpf执行完异步之后还是原来的线程是因为这个是ui主线程。

  1. 同步方法是在调用的时候就要等待返回结果。而异步方法,不是必须要在调用的时候就等着拿结果的。比如例子1的第9行,我读取文本的时候返回了一个Task。并不是马上拿到结果。先进行网络请求后,然后再await等结果。相当于读取文本和网络请求是在并行执行的。 :::

    1. async static Task Main(string[] args) {
    2. StringBuilder sb = new StringBuilder();
    3. for (int i = 0; i < 2000000; i++) {
    4. sb.Append("ahahahhaahahahhah");
    5. }
    6. Console.WriteLine("开始写入文本,线程id:" + Thread.CurrentThread.ManagedThreadId);
    7. await File.AppendAllTextAsync(@"E:\测试\a.txt", sb.ToString());
    8. Console.WriteLine("结束写入文本,且开始读取文本,线程id:" + Thread.CurrentThread.ManagedThreadId);
    9. Task<string> task = File.ReadAllTextAsync(@"E:\测试\a.txt");
    10. HttpClient client = new HttpClient();
    11. Console.WriteLine("开始网络请求,线程id:" + Thread.CurrentThread.ManagedThreadId);
    12. HttpResponseMessage response = await client.GetAsync("https://baidu.com");
    13. Console.WriteLine("结束网络请求,线程id:" + Thread.CurrentThread.ManagedThreadId);
    14. string txt = await task;
    15. Console.WriteLine("结束读取文本,线程id:" + Thread.CurrentThread.ManagedThreadId);
    16. }

    image.png

    1. private async void Button_Click(object sender, RoutedEventArgs e) {
    2. StringBuilder sb = new StringBuilder();
    3. for (int i = 0; i < 2000000; i++) {
    4. sb.Append("ahahahhaahahahhah");
    5. }
    6. txt.Text += "开始写入文本,线程id:" + Thread.CurrentThread.ManagedThreadId+"\n";
    7. await File.AppendAllTextAsync(@"E:\测试\a.txt", sb.ToString());
    8. txt.Text += "结束写入文本,且开始读取文本,线程id:" + Thread.CurrentThread.ManagedThreadId+"\n";
    9. Task<string> task = File.ReadAllTextAsync(@"E:\测试\a.txt");
    10. HttpClient client = new HttpClient();
    11. txt.Text += "开始网络请求,线程id:" + Thread.CurrentThread.ManagedThreadId+"\n";
    12. HttpResponseMessage response = await client.GetAsync("https://baidu.com");
    13. txt.Text += "结束网络请求,线程id:" + Thread.CurrentThread.ManagedThreadId + "\n";
    14. string t = await task;
    15. txt.Text += "结束读取文本,线程id:" + Thread.CurrentThread.ManagedThreadId + "\n";
    16. }

    image.png