Csharper Async和Await

Async和Await的学习

AsyncAwait语法解读

它是一个语法糖:编译器提供的便捷功能
async 是用来修饰方法,如果单独出现,方法会警告,没有什么作用
await在方法体内部,只能放在async修饰的方法内,必须放在task前面
async/await方法里面如果没有返回值,默认返回一个Task,或者void(推荐用Task,而不是void,因为这样才能await/wait)
带async+await后,返回值要多一层Task<>

不使用await/async的代码进行运行

    public static void TestShow()
    {
        Test();
    }


    public static void Test()
    {
        Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        {
            NoReturnNoAwait();
        }

        Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
        Console.Read();
    }

    private static async void NoReturnNoAwait()
    {
        //主线程执行
        Task task = Task.Run(() =>//启动新线程完成任务
        {
            Thread.Sleep(1000);
            Console.WriteLine($"NoReturnNoAwait Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            Console.WriteLine($"NoReturnNoAwait Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
        });

        //主线程执行
        Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    }
}

运行后的结果

运行另外一个函数并不适用async:

   public static void Test()
   {
       //Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
       //{
       //     NoReturnNoAwait();
       //}

       {
           Task t = NoReturn();
           t.Wait();//主线程在这里就可以登录
           {
               for (int i = 0; i < 10; i++)
               {
                   Thread.Sleep(300);
                   Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")} i={i}");
               }
           }
       }
}


 private static Task NoReturn()
 {
     //主线程执行
     Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     TaskFactory taskFactory = new TaskFactory();
     Task task = taskFactory.StartNew(() =>
     {
         Console.WriteLine($"NoReturn Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
         Thread.Sleep(3000);
         Console.WriteLine($"NoReturn Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     });

     //像什么?continuwith

     //task.ContinueWith(t =>
     //{
     //    Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     //});

     return task;
}

使用async和await关键字的方法

 public static void Test()
 {
         {
         Task t = NoReturnTask();
         Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
}
}

  private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
  {
      //这里还是主线程的id
      Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");

      Task task = Task.Run(() =>
      {
          Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
          Thread.Sleep(1000);
          Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
      });
      await task;
      Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

可以观察到我们的代码都是并行的。

  1. await: 线程遇到awai就返回,不阻塞:并发执行
  2. 做到控制顺序
    其实就是以写同步方法 的方式;来完成多线程:--可以控制顺序
  3. 主线程和子线程是并发执行
  4. 有点类似于一个回调
  5. 有async/await 如果没有返回值,就直接返回一个Task

特点:
l. 可以多线程并发执行,可以控制代码的执行顺序一一做到了严格控制代码的执行顺序;
2.执行的特点:当前执行的线程,调用的有asyncawait修饰的函数,且没有await修饰,进入函数后,遇到await,就直接返回执行调用当前函数后面的代码:如果当前调用也用了await修饰,就严格按照顺序执行:不存在遇到await回去继续往后执行:
3.await启动线程的后面的内容,又会启动一个新的线程来继续往后执行;以此往复;

AsyncAwait底层源码解读(状态机)

async和await在底层是一个状态机(状态模式),类似红绿灯,红灯:停,绿灯:行,按照条件执行,像switch

示例代码

 public class AwaitAsyncILSpy
 {
     public static void Show()
     {
         Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         Async();
         Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
     }
     public static async void Async()
     {
         Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         await Task.Run(() =>
         {
             Thread.Sleep(500);
             Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         });
         Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
     }
 }

使用Ilspy反编译以后

看到IL反汇编出来的代码,我们可以知道:

  1. 实例化状态机
  2. 把状态机实例交给一个build去执行
  3. 整理线程的上下文
  4. stateMachine.MoveNext(); 调用MoveNext方法
  5. MoveNext如何执行:先获取一个状态 ---继续往后执行
  6. 如果有异常---一抛出异常--把状态重置为-2
  7. 如果没有异常,把状态重置重置为-2
  8. SetResult();---把结果包裹成一个Tsak

Winform中使用async/await

这里创建一个winform的程序并实现了2个按钮

同步按钮得代码:

 private void btnSync_Click(object sender, EventArgs e)
 {
     Debug.WriteLine($"**********************************btnSync_Click******************************************");
     Debug.WriteLine($"This is btnSync_Click Start,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     var task = this.CalculationAsync(1_000_000);           
   
     long lResult = task.Result;      

     Debug.WriteLine($"This is btnSync_Click   End,ThreadId={Thread.CurrentThread.ManagedThreadId}");

     this.textSync.Text = lResult.ToString();
 }

异步按钮得代码:

 /// <summary>
 ///异步方法: 正常执行
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private async void btnAsync_Click(object sender, EventArgs e)
 {
     Debug.WriteLine($"**********************************btnAsync_Click******************************************");
     Debug.WriteLine($"This is btnAsync_Click Start,ThreadId={Thread.CurrentThread.ManagedThreadId}");

     long lResult = await this.CalculationAsync(1_000_000);

     Debug.WriteLine($"This is btnAsync_Click   End,ThreadId={Thread.CurrentThread.ManagedThreadId}");

     this.textAsyncResult.Text = lResult.ToString();
  
 }

对应计算得代码:

 private async Task<long> CalculationAsync(long total)
 {
     var task = await Task.Run(() =>
       {
           Debug.WriteLine($"This is CalculationAsync Start,ThreadId={Thread.CurrentThread.ManagedThreadId}");
           long lResult = 0;
           for (int i = 0; i < total; i++)
           {
               lResult += i;
           }
           Debug.WriteLine($"This is CalculationAsync   End,ThreadId={Thread.CurrentThread.ManagedThreadId}");

           return lResult;
       });

     return task; 
 }

当点击同步按钮得时候就会遇到一个问题,程序卡死了,为什么会造成这个问题?

  1. 主线程要等待 Winform特殊设计:await后面的内容必须由主线程执行;
  2. 主线程在这儿也等待着在
  3. 主线程无暇分身导致死锁

更改控件的值,这里必须是(UI线程)主线程去执行;
每次执行都能成功,说明每次这里都是主线程来执行的;
跟Winform设计有关系;在Winform中,await后面的内容,都会让主线程来执行;

因此造成了主线程得死锁。

posted @ 2024-07-25 17:47  飘雨的河  阅读(55)  评论(0编辑  收藏  举报