.NET(C#) TPL:Task中未觉察异常和TaskScheduler.UnobservedTaskException事件

本文介绍如何处理C#中Task执行时产生的未觉察异常,并提供了几种方法来捕获这些异常,包括使用Task.Wait/WaitAll、Task<T>.Result属性、Task.Exception属性、TaskContinuationOptions.OnlyOnFaulted选项以及TaskScheduler.UnobservedTaskException事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当你在一个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,没有异常抛出。

转载自:http://www.mgenware.com/blog/?p=231

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值