1. 程式人生 > 其它 >async/await Task.Delay 和Thread.Sleep的理解

async/await Task.Delay 和Thread.Sleep的理解

async/await Task.Delay 和Thread.Sleep的理解

相關學習資料:

第十七節:從狀態機的角度async和await的實現原理(新) - Yaopengfei - 部落格園 (cnblogs.com)

[基礎知識]有限狀態機_嗶哩嗶哩_bilibili

C# async await 原理:編譯器如何將非同步函式轉換成狀態機 | 碼農網 (codercto.com)

await——呼叫的等待期間,.NET會把當前的執行緒返回給執行緒池,等非同步方法呼叫執行完畢後,框架會從執行緒池再取出來一個執行緒執行後續的程式碼,當前執行緒不會阻塞

從原理層面刨析

  1. async與await是語法糖

    ,最終譯成“狀態機呼叫”。其他常用的語法糖var、using、lambda表示式等。

  2. async關鍵字標記的方法會被C#編譯器編譯成一個狀態機。

  3. await是關鍵字是為了實現狀態機中的一個狀態。

    會根據方法內的await呼叫切分成多個狀態,每當有一個await,就會生成一個對應的狀態。

  4. 狀態機實現了AsyncStateMachine介面,裡面有MoveNext 和 SetStateMachine方法處理相應業務.

    MoveNext——定義各個狀態之間轉換的方法

    1. 在await執行時呼叫一次,在await操作結束時繼續呼叫

從實踐方向刨析

  1. async會將方法的返回結果包裝成Task,所以它不是必需的

            //使用async,實際是將Result包裝成Task<Result>
            //此處就是對結果通過await拆包,再通過async包裝成Task<Result>返回,當前場景下沒有必要,可直接簡寫成下面的場景
            public static async Task AsyncNoReturn()
            {
               await Task.Delay(500);
            }
            //不使用async的寫法,因為型別已經是Task了不需要通過async關鍵字包裝
            public static  Task AsyncNoReturn1()
            {
                return Task.Delay(500);
            }
    

    總結:如果一個非同步方法只是對別的非同步方法呼叫的轉發,並沒有太多複雜的邏輯(比如等待的結果,再呼叫B:把呼叫的返回值拿到內部做一些處理再返回),那麼就可以去掉async關鍵字。返回值為Task的方法不一定都要標註async,標註async只是讓我們可以更方便的await而已

    1. 避免對返回結果Task的“拆包後再次包裝”
    2. 避免在底層建立狀態機,效能更優

    正面Demo:

            public static Task<string> DownloadFromFile(int num)
            {
                if (num == 1)
                {
                   return File.ReadAllTextAsync(@"D:\1.txt");
                }
                else if (num == 2)
                {
                    return File.ReadAllTextAsync(@"D:\2.txt");
                }
                else
                {
                    throw new ArgumentException("Invalid Number");
                }
            }
    

    如果沒有在宣告Task的時候前面沒有加await,則主執行緒不會非同步等待Task完成,主執行緒會繼續往下執行,與Task併發執行

    Task.Delay() 和 Thread.Sleep() 區別

Thread.Sleep() ——會讓當前執行緒休眠,形成阻塞,當前休眠結束後繼續往下執行

await Task.Delay()——當前執行緒返回執行緒池,從執行緒池拿另外一個空閒執行緒執行緒做定時任務,等待任務結束後,從執行緒池拿到一個空閒執行緒,繼續往下執行。

            await Task.Delay(200);
            //可理解等效成如下程式碼
            await Task.Run(() =>
            {
                Thread.Sleep(200);
            });

區別:

  1. Thread.Sleep會阻塞當前執行緒,但是從執行緒池另取執行緒;Task.Delay不會阻塞當前執行緒,但是會新取一個執行緒

  2. Thread.Sleep不可取消,Task.Delay可取消

            public static Task Test_Delay()
            {
                //建立一個5秒的非同步等待
                Task delay1 = Task.Delay(TimeSpan.FromSeconds(5));
                return delay1;
            }
    

    在Main方法裡測試

    static void Main(string[] args)
    {
        //Task在宣告處就開始執行
    	var test = Test_Delay();
        //主執行緒等待2秒鐘
    	Thread.Sleep(TimeSpan.FromSeconds(2));
    	Stopwatch sw = new Stopwatch();
    	sw.Start();
        //當前執行緒等待test指向的Task執行結束
    	test.Wait();
    	sw.Stop();
    	TimeSpan ts = sw.Elapsed;
    	Console.WriteLine($"Task.Delay執行結束,耗時:{ts.TotalMilliseconds}");
    	Console.ReadKey();
    }
    

    結果列印:

    Task.Delay執行結束,耗時:3004.527
    

    上面顯示大概監聽為3秒。這個表示了當前主執行緒在執行的時候,Task.Delay也在執行,這個只有在不同執行緒才可以實現。