async/await Task.Delay 和Thread.Sleep的理解
相關學習資料:
第十七節:從狀態機的角度async和await的實現原理(新) - Yaopengfei - 博客園 (cnblogs.com)
C# async await 原理:編譯器如何將異步函數轉換成狀態機 | 碼農網 (codercto.com)
await——調用的等待期間,.NET會把當前的線程返回給線程池,等異步方法調用執行完畢后,框架會從線程池再取出來一個線程執行后續的代碼,當前線程不會阻塞
從原理層面刨析
-
async與await是
語法糖,最終譯成“狀態機調用”。其他常用的語法糖var、using、lambda表達式等。 -
async關鍵字標記的方法會被C#編譯器編譯成一個狀態機。
-
await是關鍵字是為了實現狀態機中的一個狀態。
會根據方法內的await調用切分成多個狀態,每當有一個await,就會生成一個對應的狀態。
-
狀態機實現了AsyncStateMachine接口,里面有MoveNext 和 SetStateMachine方法處理相應業務.

MoveNext——定義各個狀態之間轉換的方法
- 在await執行時調用一次,在await操作結束時繼續調用
從實踐方向刨析
-
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而已
- 避免對返回結果Task的“拆包后再次包裝”
- 避免在底層創建狀態機,性能更優
正面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並發執行
Thread.Sleep() ——會讓當前線程休眠,形成阻塞,當前休眠結束后繼續往下執行
await Task.Delay()——當前線程返回線程池,從線程池拿另外一個空閑線程線程做定時任務,等待任務結束后,從線程池拿到一個空閑線程,繼續往下執行。
await Task.Delay(200);
//可理解等效成如下代碼
await Task.Run(() =>
{
Thread.Sleep(200);
});
區別:
-
Thread.Sleep會阻塞當前線程,但是從線程池另取線程;Task.Delay不會阻塞當前線程,但是會新取一個線程
-
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也在運行,這個只有在不同線程才可以實現。
