如何使用 C# 在异步代码中处理异常
異常處理是一種處理運(yùn)行時(shí)錯(cuò)誤的技術(shù),而 異步編程 允許我們?cè)谔幚碣Y源密集型的業(yè)務(wù)邏輯時(shí)不需要在 Main 方法或者在 執(zhí)行線(xiàn)程 中被阻塞,值得注意的是,異步方法和同步方法的異常處理機(jī)制是不一樣的,本篇我們就來(lái)討論下如何在異步方法中處理異常。
異步方法 VS 同步方法 的異常處理
在同步代碼中拋出異常,它會(huì)一直以冒泡的方式往上拋,直到遇到可以處理這個(gè)異常的 catch 塊為止,可以想象,異步方法中的異常拋出肯定要比這個(gè)復(fù)雜。
大家都知道 異步方法 可以有三種返回類(lèi)型,如:void, Task, Task<TResult>,當(dāng)異常方法的返回值是 Task ,Task<TResult> 的方法中拋出異常的話(huà),這個(gè)異常對(duì)象會(huì)被塞到 AggregateException 對(duì)象中,然后包裹在 Task 中進(jìn)行返回,有些朋友可能要問(wèn),如果異步方法中拋出了幾個(gè)異常怎么辦?其實(shí)也是一樣的道理,這些異常對(duì)象都會(huì)被塞到 AggregateException 中通過(guò) Task 去返回。
最后,如果異常出現(xiàn)在返回值為 void 的異步方法中,異常是在調(diào)用這個(gè)異步方法的 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 方法拋出的異常會(huì)被包裹此方法的 try catch 捕捉到。
返回 Task 的異步方法異常
當(dāng)異常從返回值為 Task 的異步方法中拋出,這個(gè)異常對(duì)象會(huì)被包裹在 Task 中并且返回給方法調(diào)用方,當(dāng)你用 await 等待此方法時(shí),只會(huì)得到一組異常中的第一個(gè)被觸發(fā)的異常,如果有點(diǎn)懵的話(huà),如下代碼所示:
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 都會(huì)拋出異常,但在 catch 塊中只捕獲了 task1 中的異常,這就說(shuō)明返回值為 Task 的多個(gè)異常的方法中,調(diào)用方只能截獲第一次發(fā)生異常的異常對(duì)象。
使用 Exceptions 屬性 獲取所有異常
要想獲取已拋出的所有異常,可以利用 Task.Exceptions 屬性來(lái)獲取,下面的代碼清單展示了如何在返回 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 屬性去處理一組異常中的某一個(gè),同時(shí)忽略其他你不關(guān)心的異常,下面的代碼片段展示了如何去實(shí)現(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 會(huì)被處理, InvalidOperationException 會(huì)被忽略。
最后想說(shuō)的是,你可以利用 異步編程 來(lái)提高程序的擴(kuò)展性和吞吐率,當(dāng)你在使用異步方法時(shí),請(qǐng)注意在異步方法中的異常處理語(yǔ)義和同步方法中的異常處理是不一樣的。
譯文鏈接:https://www.infoworld.com/article/3453659/how-to-handle-exceptions-in-asynchronous-code-in-c.html
總結(jié)
以上是生活随笔為你收集整理的如何使用 C# 在异步代码中处理异常的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .Net Conf 2020 之回顾
- 下一篇: 对 Redis 中的有序集合Sorted