新聞速報

        

2017年4月28日 星期五

Task中未覺察異常和TaskScheduler.UnobservedTaskException事件

當你在一個Task執行中拋出異常,比如: iew sourceprint?
1.Task.Factory.StartNew(() =>
2.{
3.throw new Exception();
4.});
運行該方法,沒有任何異常拋出。

事實上此時Task的異常處於未覺察狀態,這個未覺察狀態的異常會在垃圾回收時終結器執行線程中被拋出。

為了誘發這個異常,我們可以通過GC.Collect來強制垃圾回收從而引發終結器處理線程,此時Task的未覺察異常會被拋出。
01.//在Task中拋出異常
02. 
03.Task.Factory.StartNew(() =>
04.{
05.throw new Exception();
06.});
07. 
08. 
09.//確保任務完成
10.Thread.Sleep(100);
11.//強制垃圾會受到
12.GC.Collect();
13.//等待終結器處理
14.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:
01.try
02.{
03.Task.WaitAll(
04.Task.Factory.StartNew(() =>
05.{
06.throw new Exception();
07.}));
08.}
09.catch (AggregateException)
10.{ }
11. 
12.//確保任務完成
13.Thread.Sleep(100);
14.//強制垃圾會受到
15.GC.Collect();
16.//等待終結器處理
17.GC.WaitForPendingFinalizers();
這樣就不會有任何異常拋出(即使是終結器線程已經結束)。

當然最簡單的就是直接引用一下Task.Exception屬性:
注意這裡使用Task.ContinueWith是為了避免直接引用Task變量,這樣垃圾回收可以處理這個Task對象!
01.//使用Task.ContinueWith可以避免直接引用Task變量,這樣垃圾回收可以處理這個Task對象!
02. 
03.Task.Factory.StartNew(() =>
04.{
05.throw new Exception();
06.}).ContinueWith(t => { var exp = t.Exception; });
07. 
08.//确保任务完成
09.Thread.Sleep(100);
10.//强制垃圾会受到
11.GC.Collect();
12.//等待终结器处理
13.GC.WaitForPendingFinalizers();
同樣不會有異常拋出。

另外,可以通過TaskContinuationOptions.OnlyOnFaulted來使引用Exception屬性只發生在發生異常時(即Exception為null的時候沒必要再去引用它),代碼:
1.Task.Factory.StartNew(() =>
2.{
3.throw new Exception();
4.}).ContinueWith(t => { var exp = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
最後是TaskScheduler.UnobservedTaskException事件,該事件是所有未覺察異常被拋出前的最後可以將其覺察的方 法。

通過UnobservedTaskExceptionEventArgs.SetObserved方法來將異常標記為已覺察。
代碼:
01.最后是TaskScheduler.UnobservedTaskException事件,该事件是所有未觉察异常被抛出前的最后可以将其觉察的方法。通过UnobservedTaskExceptionEventArgs.SetObserved方法来将异常标记为已觉察。
02. 
03.代码:
04. 
05.TaskScheduler.UnobservedTaskException += (s, e) =>
06.{
07.//設置所有未覺察異常被覺察
08.e.SetObserved();
09.};
10. 
11.Task.Factory.StartNew(() =>
12.{
13.throw new Exception();
14.});
15. 
16.//確保任務完成
17.Thread.Sleep(100);
18.//強制垃圾會受到
19.GC.Collect();
20.//等待終結器處理
21.GC.WaitForPendingFinalizers();

OK,沒有異常拋出。

沒有留言:

張貼留言