第一點需要提的就是第一個await發生異常後,後續的await不會進行,但是注意此時的Task仍然會進行的,當然後面的Task如果拋出異常的話,異常則屬於未覺察異常,這個未覺察異常在.NET 4.0會在記憶體回收時引發程式崩潰,但在.NET 4.5 beta後不會引發進程崩潰。更多相關資訊可以參(.NET 4.5中關於Task的未覺察異常的更新),這裡就不多講了。
當第一個異常被拋出後,我們可以直接在這個await上進行try和catch來捕獲異常,就像該方法被同步執行一樣,而事實上,這個方法會被await而分割成兩半,前面的會同步執行,而await後面的代碼則會被附加在對應Task結束後執行(如果Task在此時沒有結束的話),同時當前環境的SynchronizationContext會被用來調用await後的代碼。
當然編譯器做了相當大的功勞,讓我們可以簡單的按照普通異常處理的方式來處理非同步呼叫的異常處理。
來看下面這個叫doo的async方法,方法運行開始會運行兩個Task,分別在1秒後和4秒後拋出異常,然後在後面await這兩個Task,代碼:
static async Task doo()
{
try
{
Console.WriteLine("ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
var t1 = Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("Task1即將拋出異常"); throw new Exception("Task1異常"); });
var t2 = Task.Run(() => { Thread.Sleep(4000); Console.WriteLine("Task2即將拋出異常"); throw new Exception("Task2異常"); });
await t1;
Console.WriteLine("Task1的await執行完畢");
await t2;
Console.WriteLine("Task2的await執行完畢");
}
catch (Exception ex)
{
Console.WriteLine("ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("捕獲異常:" + ex.Message);
}
}
接著在主方法中調用另一個async方法來await整個doo方法返回的Task,代碼如下:
static void Main(string[] args)
{
test();
Thread.Sleep(Timeout.Infinite);
}
static async void test()
{
await doo();
Console.WriteLine("test方法結束");
}
等待幾秒後,整個程式的完整輸出是這樣:
ManagedThreadId: 1
Task1即將拋出異常
ManagedThreadId: 3
捕獲異常:Task1異常
test方法結束
Task2即將拋出異常
可以看到,當Task1拋出異常後,異常被捕獲,Task2不會被await,當然Task2仍然在運行。
從代碼上看,try-catch在同一個方法內,給人造成是同步執行的假象,事實上,通過輸出當前線程的ManagedThreadId我們可以看到:執行try和catch代碼地區的完全是兩個線程。
最後如果在此類Task上再調用Wait會有什麼結果?答案則是同普通Wait一樣,需要捕獲AggregateException異常,然後通過它的InnerExceptions屬性訪問包含的子異常,不過同await的結果也一樣,此時異常也只會是第一個await中Task的異常,後續Task異常值會被默默吞掉(如果這些Task外部沒有引用的話),同時Wait方法不會等待其他沒有await的Task的執行。
範例程式碼:
首先把doo中的try-catch去掉。
然後主方法:
static void Main(string[] args)
{
try
{
doo().Wait();
}
catch (AggregateException ae)
{
foreach(var exp in ae.InnerExceptions)
Console.WriteLine(exp.Message);
Console.WriteLine("結束catch");
}
Thread.Sleep(Timeout.Infinite);
}
結果:
Task1即將拋出異常
Task1異常
結束catch
Task2即將拋出異常
當catch結束後,另一個Task的異常才被拋出,Wait沒有等它,同時AggregateException也不包含另一個Task的異常。