當你在一個Task執行中拋出異常,比如:
Task.Factory.StartNew(() =>
{
throw new Exception();
});
運行該方法,沒有任何異常拋出。
事實上此時Task的異常處於未覺察狀態,這個未覺察狀態的異常會在記憶體回收時終結器執行線程中被拋出。
為了誘發這個異常,我們可以通過GC.Collect來強制記憶體回收從而引發終結器處理線程,此時Task的未覺察異常會被拋出。
//在Task中拋出異常
Task.Factory.StartNew(() =>
{
throw new Exception();
});
//確保任務完成
Thread.Sleep(100);
//強制垃圾會受到
GC.Collect();
//等待終結器處理
GC.WaitForPendingFinalizers();
OK,異常拋出,程式崩潰,如下輸出:
Unhandled Exception: System.AggregateException: A Task's exception(s) were not
bserved either by Waiting on the Task or accessing its Exception property. As a
result, the unobserved exception was rethrown by the finalizer thread. ---> Sys
em.Exception: Exception of type 'System.Exception' was thrown.
at Mgen.Program.<Main>b__0() in E:\Users\Mgen\Documents\Visual Studio 2010\P
ojects\Mgen\Mgen\Program.cs:line 19
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.TaskExceptionHolder.Finalize()
我們可以通過調用Task.Wait/WaitAll,或者引用Task<T>.Result屬性,或者最簡單的引用Task.Exception屬性來使Task的異常被覺察。比如這樣:
通過Task.Wait手動捕獲AggregateException:
try
{
Task.WaitAll(
Task.Factory.StartNew(() =>
{
throw new Exception();
}));
}
catch (AggregateException)
{ }
//確保任務完成
Thread.Sleep(100);
//強制垃圾會受到
GC.Collect();
//等待終結器處理
GC.WaitForPendingFinalizers();
這樣就不會有任何異常拋出(即使是終結器線程已經結束)。
當然最簡單的就是直接引用一下Task.Exception屬性:
注意這裡使用Task.ContinueWith是為了避免直接引用Task變數,這樣記憶體回收可以處理這個Task對象!
//使用Task.ContinueWith可以避免直接引用Task變數,這樣記憶體回收可以處理這個Task對象!
Task.Factory.StartNew(() =>
{
throw new Exception();
}).ContinueWith(t => { var exp = t.Exception; });
//確保任務完成
Thread.Sleep(100);
//強制垃圾會受到
GC.Collect();
//等待終結器處理
GC.WaitForPendingFinalizers();
同樣不會有異常拋出。
另外,可以通過TaskContinuationOptions.OnlyOnFaulted來使引用Exception屬性只發生在發生異常時(即Exception為null的時候沒必要再去引用它),代碼:
Task.Factory.StartNew(() =>
{
throw new Exception();
}).ContinueWith(t => { var exp = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
最後是TaskScheduler.UnobservedTaskException事件,該事件是所有未覺察異常被拋出前的最後可以將其覺察的方法。通過UnobservedTaskExceptionEventArgs.SetObserved方法來將異常標記為已覺察。
代碼:
TaskScheduler.UnobservedTaskException += (s, e) =>
{
//設定所有未覺察異常被覺察
e.SetObserved();
};
Task.Factory.StartNew(() =>
{
throw new Exception();
});
//確保任務完成
Thread.Sleep(100);
//強制垃圾會受到
GC.Collect();
//等待終結器處理
GC.WaitForPendingFinalizers();
OK,沒有異常拋出。
注意在.NET 4.5後,這個現象已改變,請參考:
.NET 4.5(C#):關於Task的未覺察異常的更新