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


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

    }
}



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年5月    »
12345
6789101112
13141516171819
20212223242526
2728293031
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864