async/await Task.Delay 和Thread.Sleep的理解
async/await Task.Delay 和Thread.Sleep的理解
相關學習資料:
第十七節:從狀態機的角度async和await的實現原理(新) - Yaopengfei - 部落格園 (cnblogs.com)
C# async await 原理:編譯器如何將非同步函式轉換成狀態機 | 碼農網 (codercto.com)
await——呼叫的等待期間,.NET會把當前的執行緒返回給執行緒池,等非同步方法呼叫執行完畢後,框架會從執行緒池再取出來一個執行緒執行後續的程式碼,當前執行緒不會阻塞
從原理層面刨析
-
async與await是
語法糖
-
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也在執行,這個只有在不同執行緒才可以實現。