C# :异步编程的注意点
在上一篇《C#:異步編程中的 async 和 await》 中簡單介紹了在 C# 中的異步編程以及 async 和 await 編程模型,本文介紹下異步編程的注意事項,主要有以下幾個方面。
同步中調用異步
在同步代碼中調用異步代碼,容易導致死鎖,所以在實際使用異步編程時,推薦的做法是一直異步到底。先來看一個會出現死鎖的代碼:
class?Program {static?void?Main(string[]?args){while?(true){Task.Run(MethodSync);Thread.Sleep(100);}}static?void?MethodSync(){//string?result?=MethodAsync().Result;MethodAsync().Wait();}static?async?Task<string>?MethodAsync(){await?Task.Run(()?=>{Thread.Sleep(2000);});Console.WriteLine("MethodAsync?End");return?"success";} }Main ?方法中使用 Task.Run 進行新的任務的創建,每個間隔 100 ?毫秒,模擬多次請求;
在同步方法 MethodSync 中調用異步方法 MethodAsync;
同步方法中使用 .Result ?或者調用 Wait() 方法進行等待;
運行上面代碼,控制臺會輸出幾次 MethodAsync End 后就會停止,這時死鎖已經發生。可以觀察到控制臺程序使用的線程數會不斷增加:
發生死鎖的原因是:程序運行時,有一個線程 A 開始執行同步方法 MethodSync ,執行到同步方法中的 .Result 或 Wait() 時,會產生一個線程 B 進行異步方法的調用;
線程 A 會等著 線程 B 完成,然后線程 A 才繼續執行后面的代碼;
當并發比較大的時候,線程池的線程不夠用,需要創建新的線程,創建線程的速度趕不上 Task 創建的速度的時候,就會造成堵塞,最終死鎖。
只需要將 MethodSync 同步方法修改為異步就可以解決此問題:
static?async?Task?MethodASync1() {await?MethodAsync(); }程序運行時,有一個線程 A 開始執行異步方法 MethodASync1 ,執行到 await 時,會產生一個線程 B 進行異步方法 MethodAsync 的調用;
線程 A 不會等著 線程 B 完成,而是會被線程池收回做其他的事情;
當線程 B 完成后,線程池會重新分配新的線程來進行后續的處理,所以整個過程不會有堵塞。
當然,有些時候我們需要在同步方法中調用異步方法,有下面兩個方法:
借助這個組件來進行處理:https://github.com/StephenCleary/AsyncEx ;
使用 ConfigureAwait(false) 。
合理使用 void 返回值
使用 void 無法確定方法在什么時候調用完成,因為沒有任何內容返回,不像 Task 的返回值,可以獲取到相關的狀態;
返回 void 的異步方法沒有辦法在調用的時候使用 await ;
對 void 方法進行調用時無法捕獲異常。
因為上面的原因,所以我們在寫代碼時盡量不要在異步方法上返回 void ,但有兩種情況也還是可以使用 void 返回值:
1、事件,比如在 Winform 程序中的按鈕事件
private?void?btnTest_Click(object?sender,?EventArgs?e)???????? {????????????await?WriteLog();???????? }如果要將 btnTest_Click 的返回值修改為 async Task ,編譯時會報錯。
2、記錄日志之類的方法,或者說該方法執行的操作和主任務關系不大,無需知道處理的結果時。
異常處理
當我們編寫同步代碼時,常用 try catch 來進行異常捕獲,例如下面代碼:
class?Program {static?void?Main(string[]?args){try{TestException();}catch?(Exception?ex){//TestException?方法拋出的異常會在這里被捕獲Console.WriteLine(ex.Message);}}static?void?TestException(){throw?new?Exception("Test?Exception");} }同樣的方式對異步方法進行 try catch ,會發現 catch 中的代碼并沒有執行:
class?Program {static?void?Main(string[]?args){try{TestExceptionAsync();}catch?(Exception?ex){//此處不會被調用Console.WriteLine(ex.Message);}Console.WriteLine("main?end");Console.ReadLine();}static?async?Task?TestExceptionAsync(){await?Task.Delay(200);throw?new?Exception("Test?TestExceptionAsync");} }要對異常方法進行異常捕獲,必須使用 await 修飾符 、調用 Wait() 方法或者訪問 Result 屬性:
static?async?Task?Main(string[]?args) {try{//var?result?=?TestExceptionAsync().Result;//TestExceptionAsync().Wait();await?TestExceptionAsync();}catch?(Exception?ex){Console.WriteLine(ex.Message);}Console.WriteLine("main?end");Console.ReadLine(); }在異步方法的返回類型 Task 類中,有一個 Exception 屬性,該屬性返回的類型為 AggregateException ,而在 AggregateException 的內部又有一個 InnerExceptions 屬性用來包裝所有異常的集合。
對于使用 await 修飾符和調用 Wait() 方法、訪問 Result 屬性對于異常的捕獲是有區別的:
Wait() 、Result
當使用Wait 或 Result 的時候,異步方法是將自身的 AggregateException 對象往上拋,這樣在異常處理的時候就會比較麻煩,我們需要這樣來進行異常的解析:
static?async?Task?Main(string[]?args) {try{TestExceptionAsync().Wait();}catch?(AggregateException?aggregateException){foreach?(var?ex?in?aggregateException.InnerExceptions){Console.WriteLine(ex.Message);}}Console.WriteLine("main?end");Console.ReadLine(); }如果直接獲取 aggregateException 的 Message 屬性,則會輸出:
One or more errors occurred. (Test TestExceptionAsync)
await
使用 await 修飾符,發生異常的時候,拋出的不是 AggregateException 對象,而是 AggregateException 對象中的 InnerExceptions 屬性中找出第一個返回,隨意在使用 await 修飾符的場景下,捕獲異常的寫法是符合我們編程習慣的。
static?async?Task?Main(string[]?args) {try{await?TestExceptionAsync();}catch?(Exception?ex){Console.WriteLine(ex.Message);}Console.ReadLine(); }希望本文對您有所幫助!
總結
以上是生活随笔為你收集整理的C# :异步编程的注意点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用 C# 中的 Action, F
- 下一篇: 如何在 C# 中使用 Dapper OR