当前位置:首页 > C#编程 > C#/.net框架 > 正文内容

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

Jorge2年前 (2022-05-08)C#/.net框架848

当你在一个Task执行中抛出异常,比如:

C#

Task.Factory.StartNew(() =>{
    throw new Exception();});

运行该方法,没有任何异常抛出。

事实上此时Task的异常处于未觉察状态,这个未觉察状态的异常会在垃圾回收时终结器执行线程中被抛出。


为了诱发这个异常,我们可以通过GC.Collect来强制垃圾回收从而引发终结器处理线程,此时Task的未觉察异常会被抛出。

C#
//在Task中抛出异常Task.Factory.StartNew(() =>{
    throw new Exception();});//确保任务完成Thread.Sleep(100);//强制垃圾会受到GC.Collect();//等待终结器处理GC.WaitForPendingFinalizers();

 


OK,异常抛出,程序崩溃,如下输出:

C#
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:

C#
try{
    Task.WaitAll(
        Task.Factory.StartNew(() =>
        {
            throw new Exception();
        }));}catch (AggregateException){ }//确保任务完成Thread.Sleep(100);//强制垃圾会受到GC.Collect();//等待终结器处理GC.WaitForPendingFinalizers();

这样就不会有任何异常抛出(即使是终结器线程已经结束)。


 


当然最简单的就是直接引用一下Task.Exception属性:


注意这里使用Task.ContinueWith是为了避免直接引用Task变量,这样垃圾回收可以处理这个Task对象!

C#
//使用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的时候没必要再去引用它),代码:

C#
Task.Factory.StartNew(() =>{
    throw new Exception();}).ContinueWith(t => { var exp = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);


最后是TaskScheduler.UnobservedTaskException事件,该事件是所有未觉察异常被抛出前的最后可以将其觉察的方法。通过UnobservedTaskExceptionEventArgs.SetObserved方法来将异常标记为已觉察。


代码:

C#
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事件处理异常的代码,再举一个例子,它提供了两种方式处理异常,防止程序崩溃。

C#
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();
        }

    }}



#转载请注明出处 www.skcircle.com 《少有人走的路》勇哥的工业自动化技术网站。


扫描二维码推送至手机访问。

版权声明:本文由7点博客发布,如需转载请注明出处。

本文链接:http://6dot.cn/?id=56

标签: .NET.NET框架
分享给朋友:

“.NET(C#) TPL:Task中未觉察异常和TaskScheduler.UnobservedTaskException事件” 的相关文章

C# 控件闪烁问题的解决

C# 控件闪烁问题的解决

说一下解决C#下控件闪烁的几个问题,如下:  listview和datagridview显示数据闪烁 自定义控件的显示闪烁listbox滚动条拖动闪烁面板中控件过多的闪烁propertyGrid点击和修改项目缓慢的问题richtextbox控件的刷新显示问题此类问题对于界面复杂规...

C#的propertygrid控件,选择和修改项目时很慢

C#的propertygrid控件,选择和修改项目时很慢

C#的propertygrid控件是很强。可以实现类似Vitual Studio属性面板那样的效果。但是。。。。我们一直痛苦它在选择和修改项目的时候很慢。我的用法是使用控件的SelectObject来绑定数据。C# PgridMotionSpeed.SelectedObject ...

C#字符串与享元(Flyweight)模式

C#字符串与享元(Flyweight)模式

注:关注这个话题是因为看到C#的关键字 lock时,其传入引用对象。因为string也是引用对象,所以能否做为lock的参数?对于这个问题,要搞明白C#的字符串的一个特点,它使用类似于享元模式的机制。因此在lock中锁字符串是相当不安全的。下面贴子是对C#字符串与享元模式的深入讨论。写这个文章,主要...

谈谈ObservableCollection观察者集合

谈谈ObservableCollection观察者集合

注:事件很常见,但有重复的代码量。如果你想简化一下事件的编程,而且是整个程序只使用一个像事件池一样的东西,可以尝试使用下文讲的ObservableCollection观察者集合来做到。ObservableCollection<T>类表示一个动态数据集合,在添加项,移除项或刷新整个列表时,...

C#:多进程开发,控制进程数量

C#:多进程开发,控制进程数量

正在c#程序优化时,如果多线程效果不佳的情况下,也会使用多进程的方案,如下:C#System.Threading.Tasks.Task task=System.Threading.Tasks.Task.Factory.StartNew(     &...

C# 异步和等待,async/await

C# 异步和等待,async/await

 首先,重点是:异步将从根本上改变大多数代码的编写方式。是的,我相信 async/await 会比 LINQ 产生更大的影响。理解异步将在短短几年内成为基本必需品。关键字介绍让我们直接开始吧。我将使用一些稍后将阐述的概念——请继续阅读第一部分。异步方法看起来像这样:public ...

发表评论

访客

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