如何使用 C# 在异步代码中处理异常
異常處理是一種處理運行時錯誤的技術(shù),而 異步編程 允許我們在處理資源密集型的業(yè)務(wù)邏輯時不需要在 Main 方法或者在 執(zhí)行線程 中被阻塞,值得注意的是,異步方法和同步方法的異常處理機制是不一樣的,本篇我們就來討論下如何在異步方法中處理異常。
異步方法 VS 同步方法 的異常處理
在同步代碼中拋出異常,它會一直以冒泡的方式往上拋,直到遇到可以處理這個異常的 catch 塊為止,可以想象,異步方法中的異常拋出肯定要比這個復(fù)雜。
大家都知道 異步方法 可以有三種返回類型,如:void, Task, Task<TResult>,當(dāng)異常方法的返回值是 Task ,Task<TResult> 的方法中拋出異常的話,這個異常對象會被塞到 AggregateException 對象中,然后包裹在 Task 中進(jìn)行返回,有些朋友可能要問,如果異步方法中拋出了幾個異常怎么辦?其實也是一樣的道理,這些異常對象都會被塞到 AggregateException 中通過 Task 去返回。
最后,如果異常出現(xiàn)在返回值為 void 的異步方法中,異常是在調(diào)用這個異步方法的 SynchronizationContext 同步上下文上觸發(fā)。
返回 void 異步方法中的異常
下面的程序展示了返回 void 的異步方法中拋出了異常。
class?Program{static?void?Main(string[]?args){ThisIsATestMethod();Console.ReadLine();}public?static?void?ThisIsATestMethod(){try{AsyncMethodReturningVoid();}catch?(Exception?ex){Console.WriteLine(ex.Message);}}private?static?async?void?AsyncMethodReturningVoid(){await?Task.Delay(1000);throw?new?Exception("This?is?an?error?message...");}}從圖中可以看到,AsyncMethodReturningVoid 方法拋出的異常會被包裹此方法的 try catch 捕捉到。
返回 Task 的異步方法異常
當(dāng)異常從返回值為 Task 的異步方法中拋出,這個異常對象會被包裹在 Task 中并且返回給方法調(diào)用方,當(dāng)你用 await 等待此方法時,只會得到一組異常中的第一個被觸發(fā)的異常,如果有點懵的話,如下代碼所示:
class?Program{static?void?Main(string[]?args){ExceptionInAsyncCodeDemo();Console.ReadLine();}public?static?async?Task?ExceptionInAsyncCodeDemo(){try{var?task1?=?Task.Run(()?=>?throw?new?IndexOutOfRangeException("IndexOutOfRangeException?is?thrown."));var?task2?=?Task.Run(()?=>?throw?new?ArithmeticException("ArithmeticException?is?thrown."));await?Task.WhenAll(task1,?task2);}catch?(AggregateException?ex){Console.WriteLine(ex.Message);}catch?(Exception?ex){Console.WriteLine(ex.Message);}}}從上面代碼中可以看出 task1 和 task2 都會拋出異常,但在 catch 塊中只捕獲了 task1 中的異常,這就說明返回值為 Task 的多個異常的方法中,調(diào)用方只能截獲第一次發(fā)生異常的異常對象。
使用 Exceptions 屬性 獲取所有異常
要想獲取已拋出的所有異常,可以利用 Task.Exceptions 屬性來獲取,下面的代碼清單展示了如何在返回 Task 的方法中獲取所有的異常信息。
class?Program{static?void?Main(string[]?args){ExceptionInAsyncCodeDemo();Console.ReadLine();}public?static?async?Task?ExceptionInAsyncCodeDemo(){Task?tasks?=?null;try{var?task1?=?Task.Run(()?=>?throw?new?IndexOutOfRangeException("IndexOutOfRangeException?is?thrown."));var?task2?=?Task.Run(()?=>?throw?new?ArithmeticException("ArithmeticException?is?thrown."));tasks?=?Task.WhenAll(task1,?task2);await?tasks;}catch{AggregateException?aggregateException?=?tasks.Exception;foreach?(var?e?in?aggregateException.InnerExceptions){Console.WriteLine(e.GetType().ToString());}}}}使用 AggregateException.Handle 處理所有異常
你可以利用 AggregateException.Handle 屬性去處理一組異常中的某一個,同時忽略其他你不關(guān)心的異常,下面的代碼片段展示了如何去實現(xiàn)。
class?Program{static?async?Task?Main(string[]?args){await?ExceptionInAsyncCodeDemo();Console.Read();}public?static?async?Task?ExceptionInAsyncCodeDemo(){Task?tasks?=?null;try{var?task1?=?Task.Run(()?=>?throw?new?IndexOutOfRangeException("IndexOutOfRangeException?is?thrown."));var?task2?=?Task.Run(()?=>?throw?new?ArithmeticException("ArithmeticException?is?thrown."));tasks?=?Task.WhenAll(task1,?task2);await?tasks;}catch(AggregateException?ex){AggregateException?aggregateException?=?tasks.Exception;foreach?(var?e?in?aggregateException.InnerExceptions){Console.WriteLine(e.GetType().ToString());}}}}上面的代碼片段表示:IndexOutOfRangeException 會被處理, InvalidOperationException 會被忽略。
最后想說的是,你可以利用 異步編程 來提高程序的擴展性和吞吐率,當(dāng)你在使用異步方法時,請注意在異步方法中的異常處理語義和同步方法中的異常處理是不一樣的。
譯文鏈接:https://www.infoworld.com/article/3453659/how-to-handle-exceptions-in-asynchronous-code-in-c.html
總結(jié)
以上是生活随笔為你收集整理的如何使用 C# 在异步代码中处理异常的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .Net Conf 2020 之回顾
- 下一篇: 利用 C# 中的 FileSystemW