【问题标题】:TaskScheduler.UnobservedTaskException event handler never being triggeredTaskScheduler.UnobservedTaskException 事件处理程序永远不会被触发
【发布时间】:2010-07-19 19:03:17
【问题描述】:

我正在阅读一本关于 C# 任务并行库的书,并有以下示例,但从未触发 TaskScheduler.UnobservedTaskException 处理程序。谁能告诉我为什么?

TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
    eventArgs.SetObserved();
    ((AggregateException)eventArgs.Exception).Handle(ex =>
    {
        Console.WriteLine("Exception type: {0}", ex.GetType());
        return true;
    });
};

Task task1 = new Task(() => 
{
    throw new ArgumentNullException();
});

Task task2 = new Task(() => {
    throw new ArgumentOutOfRangeException();
});

task1.Start();
task2.Start();

while (!task1.IsCompleted || !task2.IsCompleted)
{
    Thread.Sleep( 5000 );
}

Console.WriteLine("done");
Console.ReadLine();

【问题讨论】:

  • 我也很好奇——这个例子不正确,因为在这个例子中不可能引发事件......
  • 这本书是:apress.com/book/view/1430229675 Pro .NET 4 Parallel Programming in C#
  • @devlife 嗨,我保留了引用,但我的 UnobservedTaskException 事件也没有在这里被触发:stackoverflow.com/questions/11831844/… 你找到解决方案了吗?

标签: c# task-parallel-library


【解决方案1】:

很遗憾,该示例永远不会向您展示您的代码。 UnobservedTaskException 只会在任务被 GC 收集且未观察到异常的情况下发生 - 只要您持有对 task1task2 的引用,GC 将永远不会收集,并且您将永远不会看到您的异常处理程序。

为了查看UnobservedTaskException 的实际行为,我会尝试以下(人为的示例):

public static void Main()
{
    TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
                {
                    eventArgs.SetObserved();
                    ((AggregateException)eventArgs.Exception).Handle(ex =>
                    {
                        Console.WriteLine("Exception type: {0}", ex.GetType());
                        return true;
                    });
                };

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

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


    Thread.Sleep(100);
    GC.Collect();
    GC.WaitForPendingFinalizers();

    Console.WriteLine("Done");
    Console.ReadKey();
}

这将显示您的消息。第一个Thread.Sleep(100) 调用为任务抛出提供了足够的时间。收集和等待强制进行 GC 收集,这将触发您的事件处理程序 2x。

【讨论】:

  • 在 .NET 4.5 中,您需要额外的巫术才能实现这一点。首先,您需要在 app.config 中启用ThrowUnobservedTaskException,其次,您需要在发布配置中构建代码(这一点仅记录在链接的 MSDN 文章的示例部分中)。我将这种“让我们默默地抑制异常”的心态归咎于浏览器中的 JavaScript :)
  • @romkyns,我不需要为我的 .NET 4.5 应用程序执行此操作。
  • ThrowUnobservedTaskException 不是必需的。仅当您想恢复到终止进程的任务异常的 4.0 行为时才需要。在 4.5 中,默认更改为不终止进程。事件处理程序不需要捕获未观察到的异常。更多信息在评论here
  • 我在 Core 3.0 中尝试过这段代码。它不会触发任何事件。此代码中缺少某些内容或整个应用程序需要额外的配置/设置。或者这种机制可能只在某些时候起作用。
【解决方案2】:

在该示例 sn-p 中不会“未观察到”异常。直到垃圾收集器摆脱 Task 实例。您必须像这样重写它:

class Program {
    static void Main(string[] args) {

        TaskScheduler.UnobservedTaskException += ( object sender, UnobservedTaskExceptionEventArgs eventArgs ) =>
        {
            eventArgs.SetObserved();
            ( (AggregateException)eventArgs.Exception ).Handle( ex =>
            {
                Console.WriteLine("Exception type: {0}", ex.GetType());
                return true;
            } );
        };

        Run();

        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine("done");
        Console.ReadLine();
    }

    static void Run() {
        Task task1 = new Task(() => {
            throw new ArgumentNullException();
        });

        Task task2 = new Task(() => {
            throw new ArgumentOutOfRangeException();
        });

        task1.Start();
        task2.Start();

        while (!task1.IsCompleted || !task2.IsCompleted) {
            Thread.Sleep(50);
        }
    }
}

不要这样做,使用 Task.Wait()。

【讨论】:

    【解决方案3】:

    我在 .NET 4.5、Visual Studio 2012(调试或发布,无所谓)上运行此代码,我确实没有ThrowUnobservedTaskException 放在我的 app.config 中:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Test
    {
        public static class Program
        {
            private static void Main()
            {
                TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    
                RunTask();
    
                Thread.Sleep(2000);
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
    
                Console.ReadLine();
            }
    
            static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
            {
                Console.WriteLine("Caught!");
            }
    
            private static void RunTask()
            {
                Task<int> task = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000); // emulate some calculation
                    Console.WriteLine("Before exception");
                    throw new Exception();
                    return 1;
                });
            }
        }
    }
    

    异常被 UnobservedTaskException 处理程序捕获(打印“Caught!”)。

    【讨论】:

    • 这是因为ThrowUnobservedTaskException 仅在您想恢复到终止进程的任务异常的 4.0 行为时才需要。在 4.5 中,默认更改为 not 终止进程。事件处理程序不需要捕获示例中未观察到的异常。更多信息在评论here
    • 在 .NET Core 3.1 中工作
    猜你喜欢
    • 2018-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-15
    • 2019-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多