当你在一个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();
}
}
}

少有人走的路


















