C#異步編程由淺入深(二)之Async/Await的使用
考慮到直接講實現(xiàn)一個類Task庫思維有點跳躍,所以本節(jié)主要講解Async/Await的本質(zhì)作用(解決了什么問題),以及Async/Await的工作原理。實現(xiàn)一個類Task的庫則放在后面講。首先回顧一下上篇博客的場景。
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); default: break; } public static void OnMOUSE_MOVE(string msg) Console.WriteLine("開始繪制鼠標形狀"); public static int Http() Thread.Sleep(1000);//模擬網(wǎng)絡(luò)IO延時 return 1; public static void HttpAsync(Action<int> action,Action error) //這里我們用另一個線程來實現(xiàn)異步IO,由于Http方法內(nèi)部是通過Sleep來模擬網(wǎng)絡(luò)IO延時的,這里也只能通過另一個線程來實現(xiàn)異步IO //但記住,多線程是實現(xiàn)異步IO的一個手段而已,它不是必須的,后面會講到如何通過一個線程來實現(xiàn)異步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); //做一些工作 }) if (t.Status == TaskStatus.Faulted) else if (t.Status == TaskStatus.RanToCompletion) Console.WriteLine(2); 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這個處理函數(shù)中,我們使用Task的ContinueWith函數(shù)進行鏈式操作,解決了回調(diào)地獄問題,但是總感覺有點那么不爽,我們假想有個關(guān)鍵字await它能實現(xiàn)以下作用:首先await必須是Task類型,必須是Task類型的(其實不是必要條件,后面會講到)原因是保證必須有ContinueWith這個函數(shù),如果Task沒有返回值,則把await后面的代碼放到Task中的ContinueWith函數(shù)體內(nèi),如果有返回值,則把Await后的結(jié)果轉(zhuǎn)化為訪問Task.Result屬性,文字說的可能不明白,看下示例代碼
//無返回值轉(zhuǎn)換前 public async void Example() { Task t = Task.Run(() => { Thread.Sleep(1000); }); await t; //做一些工作 } //無返回值轉(zhuǎn)換后 public void Example() t.ContinueWith(task => //做一些工作 //有返回值轉(zhuǎn)換前 Task<int> t = Task.Run<int>(() => return 1; int res = await t; //使用res做一些工作 //有返回值轉(zhuǎn)換后 //使用task.Result做一些工作
看起來不錯,但至少有以下問題,如下:
- 該種轉(zhuǎn)換方法不能很好的轉(zhuǎn)換Try/Catch結(jié)構(gòu)
- 在循環(huán)結(jié)構(gòu)中使用await不好轉(zhuǎn)換
- 該實現(xiàn)與Task類型緊密聯(lián)系
一二點是我自己認為的,但第三點是可以從擴展async/await這點被證明的。但無論怎樣,async/await只是對方法按照一定的規(guī)則進行了變換而已,它并沒有什么特別之處,具體來講,就是把Await后面要執(zhí)行的代碼放到一個類似ContinueWith的函數(shù)中,在C#中,它是以狀態(tài)機的形式表現(xiàn)的,每個狀態(tài)都對應(yīng)一部分代碼,狀態(tài)機有一個MoveNext()方法,MoveNext()根據(jù)不同的狀態(tài)執(zhí)行不同的代碼,然后每個狀態(tài)部分對應(yīng)的代碼都會設(shè)置下一個狀態(tài)字段,然后把自身的MoveNext()方法放到類似ContinueWith()的函數(shù)中去執(zhí)行,整個狀態(tài)機由回調(diào)函數(shù)推動。我們嘗試手動轉(zhuǎn)換以下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"); }
手動寫一個簡單的狀態(tài)機類
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"); } }
而Test()方法則變成了這樣
public static void Test() { new TestAsyncStateMachine().Start(); }
注意Test()方法返回的是void,這意味這調(diào)用方將不能await Test()。如果返回Task,這個狀態(tài)機類是不能正確處理的,如果要正確處理,那么狀態(tài)機在Start()啟動后,必須返回一個Task,而這個Task在整個狀態(tài)機流轉(zhuǎn)完畢后要變成完成狀態(tài),以便調(diào)用方在該Task上調(diào)用的ContinueWith得以繼續(xù)執(zhí)行,而就Task這個類而言,它是沒有提供這種方法(內(nèi)部有,但沒有對外暴露)來主動控制Task的狀態(tài)的,這個與JS中的Promise不同,JS里面用Reslove函數(shù)來主動控制Promise的狀態(tài),并導(dǎo)致在該Promise上面的Then鏈式調(diào)用得以繼續(xù)完成,而在C#里面怎么做呢?既然使用了狀態(tài)機來實現(xiàn)async/await,那么在轉(zhuǎn)換一個返回Task的函數(shù)時肯定會遇到,怎么處理?后面講。
首先解決一下與Task類型緊密聯(lián)系這個問題。
從狀態(tài)機中可以看到,主要使用到了Task中的ContinueWith這個函數(shù),它的語義是在任務(wù)完成后,執(zhí)行回調(diào)函數(shù),通過回調(diào)函數(shù)拿到結(jié)果,這個編程風(fēng)格也叫做CPS(Continuation-Passing-Style, 續(xù)體傳遞風(fēng)格),那么我們能不能把這個函數(shù)給抽象出來呢?語言開發(fā)者當(dāng)然想到了,它被抽象成了一個Awaiter因此編譯器要求await的類型必須要有GetAwaiter方法,什么樣的類型才是Awaiter呢?編譯器規(guī)定主要實現(xiàn)了如下幾個方法的類型就是Awaiter:
- 必須繼承INotifyCompletion接口,并實現(xiàn)其中的OnCompleted(Action continuation)方法
- 必須包含IsCompleted屬性
- 必須包含GetResult()方法
第一點好理解,第二點的作用是熱路徑優(yōu)化,第三點以后講。我們再改造一下我們手動寫的狀態(tài)機。
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"); if (taskAwaiter.IsCompleted) MoveNext(); } }
可以看到去掉了與Task中ContinueWith的耦合關(guān)系,并且如果任務(wù)已經(jīng)完成,則可以直接執(zhí)行下個任務(wù),避免了無用的開銷。
因此我們可以總結(jié)一下async/await:
- async/await只是表示這個方法需要編譯器進行特殊處理,并不代表它本身一定是異步的。
- Task類中的GetAwaiter主要是給編譯器用的。
第一點我們可以用以下例子來證明,有興趣的朋友可以自己去驗證以下,以便加深理解。
//該類型包含GetAwaiter方法,且GetAwaiter()返回的類型包含三個必要條件 public class MyAwaiter : INotifyCompletion { public void OnCompleted(Action continuation) { continuation(); } public bool IsCompleted { get; } public void GetResult() public MyAwaiter GetAwaiter() => new MyAwaiter(); }
一個測試函數(shù),注意必須返回void
public static async void AwaiterTest() { await new MyAwaiter(); Console.WriteLine("Done"); }
可以看到這是完全同步進行的。
到此這篇關(guān)于C#異步編程由淺入深(二)之Async/Await的作用.的文章就介紹到這了,更多相關(guān)C#異步編程Async/Await內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
UpdateLayeredWindow實現(xiàn)任意異形窗口使用詳解
這篇文章主要為大家介紹了UpdateLayeredWindow實現(xiàn)任意異形窗口使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09Unity輸出帶點擊跳轉(zhuǎn)功能的Log實現(xiàn)技巧詳解
這篇文章主要為大家介紹了Unity輸出帶點擊跳轉(zhuǎn)功能的Log實現(xiàn)技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11C# websocket及時通信協(xié)議的實現(xiàn)方法示例
說到websocket大家一定不會陌生,WebSocket是HTML5一種新的協(xié)議。下面這篇文章主要給大家介紹了關(guān)于C# websocket及時通信協(xié)議的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-11-11DevExpress實現(xiàn)自定義TreeListNode的Tooltip的方法
這篇文章主要介紹了DevExpress實現(xiàn)自定義TreeListNode的Tooltip的方法,需要的朋友可以參考下2014-08-08