考慮到直接講實現一個類Task庫思惟有點跳躍,因此本節主要講解Async/Await的本質做用(解決了什麼問題),以及Async/Await的工做原理。實現一個類Task的庫則放在後面講。首先回顧一下上篇博客的場景。html
class Program { public static string GetMessage() { return Console.ReadLine(); } public static string TranslateMessage(string msg) { return msg; } public static void DispatherMessage(string msg) { switch (msg) { case "MOUSE_MOVE": { OnMOUSE_MOVE(msg); break; } case "MOUSE_DOWN": { OnMouse_DOWN(msg); break; } default: break; } } public static void OnMOUSE_MOVE(string msg) { Console.WriteLine("開始繪製鼠標形狀"); } public static int Http() { Thread.Sleep(1000);//模擬網絡IO延時 return 1; } public static void HttpAsync(Action<int> action,Action error) { //這裏咱們用另外一個線程來實現異步IO,因爲Http方法內部是經過Sleep來模擬網絡IO延時的,這裏也只能經過另外一個線程來實現異步IO //但記住,多線程是實現異步IO的一個手段而已,它不是必須的,後面會講到如何經過一個線程來實現異步IO。 Thread thread = new Thread(() => { try { int res = Http(); action(res); } catch { error(); } }); thread.Start(); } public static Task<int> HttpAsync() { return Task.Run(() => { return Http(); }); } public static void OnMouse_DOWN(string msg) { HttpAsync() .ContinueWith(t => { if(t.Status == TaskStatus.Faulted) { }else if(t.Status == TaskStatus.RanToCompletion) { Console.WriteLine(1); //作一些工做 } }) .ContinueWith(t => { if (t.Status == TaskStatus.Faulted) { } else if (t.Status == TaskStatus.RanToCompletion) { Console.WriteLine(2); //作一些工做 } }) .ContinueWith(t => { if (t.Status == TaskStatus.Faulted) { } else if (t.Status == TaskStatus.RanToCompletion) { Console.WriteLine(3); //作一些工做 } }); } static void Main(string[] args) { while (true) { string msg = GetMessage(); if (msg == "quit") return; string m = TranslateMessage(msg); DispatherMessage(m); } } }
在OnMouse_DOWN這個處理函數中,咱們使用Task的ContinueWith函數進行鏈式操做,解決了回調地獄問題,可是總感受有點那麼不爽,咱們假想有個關鍵字await它能實現如下做用:首先await必須是Task類型,必須是Task類型的(其實不是必要條件,後面會講到)緣由是保證必須有ContinueWith這個函數,若是Task沒有返回值,則把await後面的代碼放到Task中的ContinueWith函數體內,若是有返回值,則把Await後的結果轉化爲訪問Task.Result屬性,文字說的可能不明白,看下示例代碼編程
//無返回值轉換前 public async void Example() { Task t = Task.Run(() => { Thread.Sleep(1000); }); await t; //作一些工做 } //無返回值轉換後 public void Example() { Task t = Task.Run(() => { Thread.Sleep(1000); }); t.ContinueWith(task => { //作一些工做 }); } //有返回值轉換前 public async void Example() { Task<int> t = Task.Run<int>(() => { Thread.Sleep(1000); return 1; }); int res = await t; //使用res作一些工做 } //有返回值轉換後 public void Example() { Task<int> t = Task.Run<int>(() => { Thread.Sleep(1000); return 1; }); t.ContinueWith(task => { //使用task.Result作一些工做 }); }
看起來不錯,但至少有如下問題,以下:c#
一二點是我本身認爲的,但第三點是能夠從擴展async/await這點被證實的。但不管怎樣,async/await只是對方法按照必定的規則進行了變換而已,它並無什麼特別之處,具體來說,就是把Await後面要執行的代碼放到一個相似ContinueWith的函數中,在C#中,它是以狀態機的形式表現的,每一個狀態都對應一部分代碼,狀態機有一個MoveNext()方法,MoveNext()根據不一樣的狀態執行不一樣的代碼,而後每一個狀態部分對應的代碼都會設置下一個狀態字段,而後把自身的MoveNext()方法放到相似ContinueWith()的函數中去執行,整個狀態機由回調函數推進。咱們嘗試手動轉換如下async/await方法。網絡
public static Task WorkAsync() { return Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("Done!"); }); } public static async void Test() { Console.WriteLine("步驟1"); await WorkAsync(); Console.WriteLine("步驟2"); await WorkAsync(); Console.WriteLine("步驟3"); }
手動寫一個簡單的狀態機類多線程
public class TestAsyncStateMachine { public int _state = 0; public void Start() => MoveNext(); public void MoveNext() { switch(_state) { case 0: { goto Step0; } case 1: { goto Step1; } default: { Console.WriteLine("步驟3"); return; } } Step0: { Console.WriteLine("步驟1"); _state = 1; WorkAsync().ContinueWith(t => this.MoveNext()); return; } Step1: { _state = -1; Console.WriteLine("步驟2"); WorkAsync().ContinueWith(t => this.MoveNext()); return; } } }
而Test()方法則變成了這樣異步
public static void Test() { new TestAsyncStateMachine().Start(); }
注意Test()方法返回的是void,這意味這調用方將不能await Test()。若是返回Task,這個狀態機類是不能正確處理的,若是要正確處理,那麼狀態機在Start()啓動後,必須返回一個Task,而這個Task在整個狀態機流轉完畢後要變成完成狀態,以便調用方在該Task上調用的ContinueWith得以繼續執行,而就Task這個類而言,它是沒有提供這種方法來主動控制Task的狀態的,這個與JS中的Promise不一樣,JS裏面用Reslove函數來主動控制Promise的狀態,並致使在該Promise上面的Then鏈式調用得以繼續完成,而在C#裏面怎麼作呢?既然使用了狀態機來實現async/await,那麼在轉換一個返回Task的函數時確定會遇到,怎麼處理?後面講。
首先解決一下與Task類型緊密聯繫這個問題。
從狀態機中能夠看到,主要使用到了Task中的ContinueWith這個函數,它的語義是在任務完成後,執行回調函數,經過回調函數拿到結果,這個編程風格也叫作CPS(Continuation-Passing-Style, 續體傳遞風格),那麼咱們能不能把這個函數給抽象出來呢?語言開發者固然想到了,它被抽象成了一個Awaiter所以編譯器要求await的類型必需要有GetAwaiter方法,什麼樣的類型纔是Awaiter呢?編譯器規定主要實現了以下幾個方法的類型就是Awaiter:async
第一點好理解,第二點的做用是熱路徑優化,第三點之後講。咱們再改造一下咱們手動寫的狀態機。函數
public class TestAsyncStateMachine { public int _state = 0; public void Start() => MoveNext(); public void MoveNext() { switch(_state) { case 0: { goto Step0; } case 1: { goto Step1; } default: { Console.WriteLine("步驟3"); return; } } Step0: { Console.WriteLine("步驟1"); _state = 1; TaskAwaiter taskAwaiter; taskAwaiter = WorkAsync().GetAwaiter(); if (taskAwaiter.IsCompleted) goto Step1; taskAwaiter.OnCompleted(() => this.MoveNext()); return; } Step1: { _state = -1; Console.WriteLine("步驟2"); TaskAwaiter taskAwaiter; taskAwaiter = WorkAsync().GetAwaiter(); if (taskAwaiter.IsCompleted) MoveNext(); taskAwaiter.OnCompleted(() => this.MoveNext()); return; } } }
能夠看到去掉了與Task中ContinueWith的耦合關係,而且若是任務已經完成,則能夠直接執行下個任務,避免了無用的開銷。
所以咱們能夠總結一下async/await:測試
第一點咱們能夠用如下例子來證實,有興趣的朋友能夠本身去驗證如下,以便加深理解。優化
//該類型包含GetAwaiter方法,且GetAwaiter()返回的類型包含三個必要條件 public class MyAwaiter : INotifyCompletion { public void OnCompleted(Action continuation) { continuation(); } public bool IsCompleted { get; } public void GetResult() { } public MyAwaiter GetAwaiter() => new MyAwaiter(); }
一個測試函數,注意必須返回void
public static async void AwaiterTest() { await new MyAwaiter(); Console.WriteLine("Done"); }
能夠看到這是徹底同步進行的。 以爲有收穫的不妨點個贊,有支持纔有動力寫出更好的文章。