## 引言 現在模擬一個非同步方法拋出了異常: ```csharp public static async Task ThrowAfter(int ms, string message) { await Task.Delay(ms); throw new Exception(message); } ` ...
引言
現在模擬一個非同步方法拋出了異常:
public static async Task ThrowAfter(int ms, string message)
{
await Task.Delay(ms);
throw new Exception(message);
}
思考一下, DontHandle()
方法是否能夠捕獲到異常?
public static void DontHandle()
{
try
{
ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
答案是:不會捕獲到異常!
因為 DontHandle()
方法在 ThrowAfter()
方法拋出異常之前,就已經執行完畢。
非同步方法的異常處理
那麼上述代碼怎麼才能捕獲到異常呢?
若想要捕獲異常則必須通過 await
關鍵字等待 ThrowAfter()
方法執行完成。
將上文中的代碼段進行修改:
public static async void HandleoOnError()
{
try
{
await ThrowAfter(1000, "first");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
結果就會輸出:
first
多個非同步方法的異常處理
如果調用兩個非同步方法,每個都會拋出異常,該如何處理呢?
我們可以這樣寫:
public static async void StartTwoTasks()
{
try
{
await ThrowAfter(1000, "first");
await ThrowAfter(1000, "second");
Console.WriteLine("StartTwoTasks is Complate");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
思考一下輸出是什麼?
答案是:
first
並沒有預想中的兩個異常都捕獲列印出來,也沒有看到“StartTwoTasks is Complate”這句話列印出來。因為使用 await
關鍵字之後,兩次調用 ThrowAfter()
方法就變成了同步執行,捕獲到第一次的異常之後直接進入到 catch
代碼段,不再執行後續代碼。
可以嘗試解決這個問題,使用 Task.WhenAll()
方法,該方法不管任務是否拋出異常,都會等到兩個任務完成。如下代碼:
public static async void StartTwoTasksParallel()
{
try
{
Task t1 = ThrowAfter(1000, "first");
Console.WriteLine("t1 is Complate");
Task t2 = ThrowAfter(1000, "second");
Console.WriteLine("t2 is Complate");
await Task.WhenAll(t2, t1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
輸出:
t1 is Complate
t2 is Complate
second
從輸出可以看出來,使用 WhenAll()
方法,兩個任務都是執行完成的,但是,捕獲異常只能捕獲 WhenAll()
方法參數中,排在最前面的,且第一個拋出異常的任務的消息,
上述方式有缺陷,只能拋出一個異常的任務的消息,可以將上面的方式再進化一下,如下代碼:
public static async void StartTwoTasksParallelEx()
{
Task t1 = null;
Task t2 = null;
try
{
t1 = ThrowAfter(1000, "first");
t2 = ThrowAfter(1000, "second");
await Task.WhenAll(t2, t1);
}
catch (Exception ex)
{
if (t1.IsFaulted)
{
Console.WriteLine(t1.Exception.InnerException.Message);
}
if (t2.IsFaulted)
{
Console.WriteLine(t2.Exception.InnerException.Message);
}
}
}
輸出:
first
second
在 try/catch
代碼塊外聲明任務變數t1、t2,使他們可以在 try/catch
塊內訪問,在這裡,使用了IsFaulted
屬性,檢查任務的狀態,若IsFaulted
屬性為 true
,則表示該任務出現異常,就可以使用 Task.Exception.InnerException
訪問異常本身。
使用AggregateException信息
除了上述方式外,還有一種更好的獲取所有任務的異常信息的方式,Task.WhenAll()
方法返回的結果其實也是一個 Task
對象,而 Task
有一個 Exception
屬性,它的類型是 AggregateException
,是 Exception
的一個派生類,AggregateException
類有一個 InnerExceptions
屬性(異常集合,包含 Task.WhenAll()
方法列表中所有異常任務的異常信息)。
有了這個屬性則可以輕鬆遍歷所有異常。如下代碼:
public static async void StartTwoTasksParallelEx2()
{
Task t3 = null;
try
{
Task t1 = ThrowAfter(1000, "first");
Task t2 = ThrowAfter(1000, "second");
await (t3 = Task.WhenAll(t2, t1));
}
catch (Exception ex)
{
foreach (var item in t3.Exception.InnerExceptions)
{
Console.WriteLine("InnerException:" + item.Message);
}
}
}
輸出:
InnerException:second
InnerException:first
總結
除了前面提到的非同步方法異常處理的基本知識點,以下是一些進階的異常處理技巧:
-
在非同步方法中,如果需要將異常傳遞給調用方,請不要直接拋出異常。相反,應該使用 throw 關鍵字將異常包裝在一個
Task
或ValueTask
對象中,並將其返回給調用方。這可以避免在非同步操作中丟失異常信息。 -
如果需要在非同步方法中處理多個異常,可以使用
catch
塊來捕獲不同類型的異常,並根據需要執行不同的處理操作。還可以使用finally
塊來執行清理操作,例如釋放資源或恢復狀態。 -
如果需要在非同步方法中執行一些非同步操作,並且這些操作都必須成功才能繼續執行下一步操作,那麼可以使用
Task.WhenAll
方法來等待所有非同步操作完成。如果任何一個非同步操作失敗,WhenAll
方法將返回一個AggregateException
對象,其中包含所有失敗的異常。 -
如果需要在非同步方法中執行多個非同步操作,並且這些操作中的任何一個失敗都將導致整個操作失敗,那麼可以使用
Task.WhenAny
方法來等待第一個非同步操作完成。如果第一個操作失敗,WhenAny
方法將返回一個AggregateException
對象,其中包含第一個失敗的異常。 -
如果需要在非同步方法中進行錯誤處理並且希望能夠獲取更多有關異常的信息,可以使用
ExceptionDispatchInfo
類。這個類可以捕獲異常並將其存儲在一個對象中,然後在需要時重新拋出異常。這可以幫助在非同步操作中保留異常信息,並將其傳遞給調用方。
總之,在非同步方法中處理異常時,需要註意一些細節和技巧,例如正確處理異常、捕獲多個異常、等待多個非同步操作、以及使用 ExceptionDispatchInfo
類來捕獲異常。掌握這些處理技巧可以幫助編寫更可靠、更健壯的非同步代碼。
作者: Niuery Daily
出處: https://www.cnblogs.com/pandefu/>
關於作者:.Net Framework,.Net Core ,WindowsForm,WPF ,控制項庫,多線程
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出 原文鏈接,否則保留追究法律責任的權利。 如有問題, 可郵件咨詢。