当你在一个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,没有异常抛出。
勇哥注:
TaskScheduler并不是采用上面的方法就能防止抛异常的。
类似于下面这些异常:
未通过等待任务或访问任务的 Exception 属性观察到任务的异常。因此,终结器线程重新引发了未观察到的异常。
已对基础计划程序成功调用 TryExecuteTaskInline,但未调用任务体。
你可能使用任何办法都无法防止它抛出(也许只是我不知道)。而且就处划能防止抛出意义也不大,因为被调度的方法没能正常执行完毕。
但是TaskScheduler的UnobservedTaskException总能捕获这些异常。
上面的异常,有种情况是 TaskScheduler调度的方法里面,有异步方法并且死锁,则每次必抛出无法捕获的异常,并且总能被 UnobservedTaskException事件触发。
另个,上文讲的UnobservedTaskException事件处理异常的代码,再举一个例子,它提供了两种方式处理异常,防止程序崩溃。
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.Remoting.Contexts; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) => { // 阻止程序崩溃的方法有2种 //第一种是: { eventArgs.SetObserved(); Console.WriteLine("Exception handled"); } //第二种,返回true if (false) { ((AggregateException)eventArgs.Exception).Handle(ex => { Console.WriteLine("Exception handled"); return true; }); } }; RunTask(); // 不断分配内存,强制让GC收集Task对象,从而触发UnobservedTaskException ArrayList arr = new ArrayList(); while (true) { char[] array = new char[100000]; arr.Add(array); GC.Collect(); } } private static void RunTask() { new Task(() => { throw new NullReferenceException(); }).Start(); } } }

