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也在運行,這個只有在不同線程才可以實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM