async/await使用深入詳解
async和await作為非同步模型程式碼編寫的語法糖已經提供了一段時間不過一直沒怎麼用,由於最近需要在BeetleX webapi中整合對Task方法的支援,所以對async和await有了深入的瞭解和實踐應用.在這總結一下async和await的使用,主要涉及到:自定義Awaitable,在傳統非同步方法中整合Task,異常處理等.
介紹
在傳統非同步方法處理都是透過指定回撥函式的方式來進行處理,這樣對於業務整非常不方便.畢竟業務資訊和狀態往往涉及到多個非同步回撥,這樣業務實現和除錯成本都非常高.為了解決這一問題dotnet推出了async和await語法糖,該語法可以把編寫的程式碼編譯成狀態機模式,從而讓開發員以同步的程式碼方式實現非同步功能的應用.
應用
async和await的使用非常簡單,只需要在方法前加上async關鍵字,然後await所有返回值為Task或ValueTask的方法即可.大概應用如下:
async void AccessTheWebAsync() { var client = new HttpClient(); var result = await client.GetStringAsync(""); Console.WriteLine(result); }
以上是HttpClient的一個簡單應用,它和傳統的同步呼叫有什麼不同呢?如果用同步GetString那執行緒回等待網路請求完成後再進行輸出,這樣會導致執行緒資源一直浪費在那裡.使用await後,當執行緒執行GetStringAsync後就會釋放出來,然後由網路回撥執行緒來觸發後面的程式碼執行.當然還有一種情況就是GetStringAsync同步完成了當執行緒就會馬上執行Console.WriteLine(result);其實不管那一種情況下都不會讓執行緒等待在那裡浪費資源.
自定義Awaitable
一般情況下async和await都是結合Task來使用,因此可能有人感覺async和await是因Task而存在的;其實async和await是一個語法糖,透過它和相應的程式碼規則來讓編譯器知道怎樣做,但這個規則並不是Task;正確的來說Task是這規則的一種實現,然後應用在大量的方法上,所以自然就使用起來就最普遍了.如果感覺Task太繁瑣使用起來比較重的情況下是完全可以自己實現這個規則,這一規則實現起來也很簡單隻需要簡單地實現一個介面和定義一些方法即可:
public interface INotifyCompletion { void OnCompleted(Action continuation); }
看上去是不是很簡單,不過除了實現這一介面外,還需要定義一些固定名稱的方法
public interface IAwaitCompletion : INotifyCompletion { bool IsCompleted { get; } void Success(object data); void Error(Exception error); } public interface IAwaitObject : IAwaitCompletion { IAwaitObject GetAwaiter(); object GetResult(); }
在基礎上再定義一下些行為就可以了,以上IAwaitObject
就是實現一個Awaitable所需要的基礎方法行為.不過Success
和'Error'方法不是必需要.只是透過這些方法可以讓外部來觸發OnCompleted
行為而已. 圍繞介面實現Awaitable的方式也可以根據實際情況應用有所不同,只要需要確保基礎規則實現即可,以下是針對SocketAsyncEventArgs
實現的Awaitable
public class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion { private static readonly Action _callbackCompleted = () => { }; private readonly PipeScheduler _ioScheduler; private Action _callback; public SocketAwaitableEventArgs(PipeScheduler ioScheduler) { _ioScheduler = ioScheduler; } public SocketAwaitableEventArgs GetAwaiter() => this; public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); public int GetResult() { Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); _callback = null; if (SocketError != SocketError.Success) { ThrowSocketException(SocketError); } return BytesTransferred; void ThrowSocketException(SocketError e) { throw new SocketException((int)e); } } public void OnCompleted(Action continuation) { if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) { Task.Run(continuation); } } public void UnsafeOnCompleted(Action continuation) { OnCompleted(continuation); } public void Complete() { OnCompleted(this); } protected override void OnCompleted(SocketAsyncEventArgs _) { var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); if (continuation != null) { _ioScheduler.Schedule(state => ((Action)state)(), continuation); } } }
以上是Kestrel內部實現的一個Awaitable,它的好處就是可以自己不停地複用,並不需要每次await都要構建一個Task物件.這樣對於大量處理的情況下可以降低物件的開銷減輕GC的負擔來提高效能.
傳統非同步下實現async/await
其實自定義Awaitable就是一種傳統非同步使用async/await功能的一種實現,但對於普通開發人員來說對於狀態不好控制的情況那實現這個Awaitable多多少少有些困難,畢竟還需要大量的測試工作來驗證.其實dotnet已經提供TaskCompletionSource<T>
物件來方便應用開發者在傳統非同步下簡單實現async/await.這個物件使用起來也非常方便
public Task<Response> Execute() { TaskCompletionSource<Response> taskCompletionSource = new TaskCompletionSource<Response>(); OnExecute(taskCompletionSource); return taskCompletionSource.Task; }
構建一個TaskCompletionSource<T>
物件返回對應的Task即可,然後在非同步完成的地方呼叫相關方法即可簡單實現傳統非同步支援async/await
taskCompletionSource.TrySetResult(response)
或
taskCompletionSource.TrySetError(exception)
在這裡不得不說一下TaskCompletionSource<T>
的設計,非要加個泛型.如果結合反射使用就有點蛋碎了,畢竟這個方法並不提供object設定,除非上層定義TaskCompletionSource<Object>
但這樣定義就失去了T的意義了....還好這個類可繼承的給使用者留了一個後路.以下做了簡單的封裝讓它支援object返回值傳入
interface IAnyCompletionSource { void Success(object data); void Error(Exception error); void WaitResponse(Task<Response> task); Task GetTask(); } class AnyCompletionSource<T> : TaskCompletionSource<T>, IAnyCompletionSource { public void Success(object data) { TrySetResult((T)data); } public void Error(Exception error) { TrySetException(error); } public async void WaitResponse(Task<Response> task) { var response = await task; if (response.Exception != null) Error(response.Exception); else Success(response.Body); } public Task GetTask() { return this.Task; } }
異常處理
由於async/await最終編譯成狀態機程式碼,所以異常處理會和普通程式碼不同,一連串的async/await方法裡,一般只需要在最頂的斷層方法Try即可,一般這個斷層的方法是async void
,或Task.wait
處;和傳統方法異常處理不一樣,如果再往上一層是無法Try住這些異常的,當現現這情況的時候往往就是未知異常導致程式死掉.以下是一個錯誤的處理程式碼:
static void Main(string[] args) { try { Test(); } catch (Exception e_) { Console.WriteLine(e_); } Console.Read(); } static async void Test() { Console.WriteLine(await PrintValue()); } static async Task<bool> PrintValue() { var value = await GetUrl(); Console.WriteLine(value); return true; } static async Task<string> GetUrl() { var client = new HttpClient(); return await client.GetStringAsync("asd"); }
正確有效的Try地方是在Test方法裡
static async void Test() { try { Console.WriteLine(await PrintValue()); } catch (Exception e_) { Console.WriteLine(e_); } } static async Task<bool> PrintValue() { var value = await GetUrl(); Console.WriteLine(value); return true; } static async Task<string> GetUrl() { var client = new HttpClient(); return await client.GetStringAsync("asd"); }
一些注意事項和技巧
自定義async/await時候,預設都是由非同步完成執行緒來觸發狀態機,但這裡存在一個風險當這個觸發狀態機的程式碼是在鎖範圍內執行就需要特別小心,很多時候再次迴歸執行獲取鎖的時候就導致無法得到引起程式碼無法執行的問題.
在使用的await之前其實是可以先判斷一下完成狀態,如果是完成就沒有必然引用await來處理狀態機的工作,這樣一定程度降低狀態的執行和開銷.
如果你的方法可以是同步完成,如一些記憶體操作那最好用ValueTask代替Task
其實反射裡使用async/await也是非常方便的,只需要判斷一下物件是否Awaitable,如果是就執行await處理狀態機.
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4687/viewspace-2819119/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- async await詳解AI
- Promise和async await詳解PromiseAI
- JavaScript中的async/await詳解JavaScriptAI
- 深入理解 async / awaitAI
- 「過程詳解」async await綜合題AI
- Async,Await 深入原始碼解析AI原始碼
- JavaScript async await 使用JavaScriptAI
- JavaScript基礎——深入學習async/awaitJavaScriptAI
- 小程式使用 async awaitAI
- async和await的使用AI
- Js中async/await的執行順序詳解JSAI
- JS中的async/await的執行順序詳解JSAI
- 如何正確使用async/await?AI
- Async/awaitAI
- Async +AwaitAI
- 深入理解 promise、generator+co、async/await 用法PromiseAI
- 深入理解 JavaScript 非同步系列(5)—— async awaitJavaScript非同步AI
- 使用async/await更好的解決非同步問題AI非同步
- async/await,瞭解一下?AI
- Vue中async await的使用示例VueAI
- 使用async await 封裝 axiosAI封裝iOS
- C# async/await 使用總結C#AI
- 理解 async/awaitAI
- async和awaitAI
- Async 和 AwaitAI
- JS非同步程式設計——深入理解async/awaitJS非同步程式設計AI
- JavaScript深入淺出非同步程式設計三、async、awaitJavaScript非同步程式設計AI
- 淺談async/awaitAI
- JavaScript Promises, async/awaitJavaScriptPromiseAI
- decorator, async/await, generatorAI
- 好用的 async/awaitAI
- C# async / awaitC#AI
- Hey async, await meAI
- 【翻譯】如何在React中使用async/await (componentDidMount Async)ReactAI
- 【譯】Async/Await(三)——Aysnc/Await模式AI模式
- async / await:更好的非同步解決方案AI非同步
- vue中async-await的使用誤區VueAI
- 停止像這樣使用 "async/await",改用原版AI