【问题标题】:Why ContinueWhenAll hides unobserved exception为什么 ContinueWhenAll 隐藏未观察到的异常
【发布时间】:2015-04-22 04:06:32
【问题描述】:

当我从任务中调用ContinueWhenAll(...) 时未观察到的异常被完全隐藏。 UnobservedTaskException 事件未引发且应用程序未终止。我等了几个小时。 当然我放了以下

<runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>

进入配置文件。

如果我评论ContinueWhenAll call - 事件被引发并且应用程序很快被杀死。而且简单的延续ContinueWith 也不会隐藏未观察到的异常。

static void Main(string[] args)
{
    TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            Console.WriteLine("From unobserved exception handler: {0}", e.Exception.Message);
        };

    var faultedTask = Task.Factory.StartNew(() => { throw new Exception("Task is faulted"); });

    // 1. ContinueWhenAll hides unobserved exception
    Task.Factory.ContinueWhenAll(new Task[] { faultedTask }, ts =>
    {
        Console.WriteLine("From \"when all\" continuation");
    });

    // 2. Simple continuation does not hide unobserved exception
    //faultedTask.ContinueWith(t => { Console.WriteLine("From \"simple\" continuation"); });

    faultedTask = null;
    int gcCounter = 0;
    while (true)
    {
        // artifical memory consumption
        int[][] a = new int[4096][];
        for (int j = 0; j < 4096; j++ )
            a[j] = new int[4096];

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

        Console.WriteLine("Garbage collected: {0}", ++gcCounter);
        Thread.Sleep(500);
    }
}

【问题讨论】:

  • Continuations 本身不会观察到异常。斯蒂芬的comment here 也这么说。 Continuations 不会观察到任务的异常,包括 ContinueWhenAll。强制观察的一种简单方法是添加对 Task.WaitAll 的调用作为 ContinueWhenAll 委托的第一行;此时任务已全部完成,因此您只是在利用 WaitAll 的观察逻辑。 所以我不确定发生了什么。编辑:更新了我的评论

标签: c# .net exception-handling task-parallel-library


【解决方案1】:

您的测试有问题。即使使用Task.ContinueWith,我也无法获得UnobservedTaskException,但是当我将触发任务的部分提取到不同的方法时,Task.ContinueWith Task.Factory.ContinueWhenAll(正如 Sriram Sakthivel 指出的那样,在发布模式下构建也具有相同的效果):

static void Main()
{
    TaskScheduler.UnobservedTaskException += (s, e) =>
    {
        Console.WriteLine("From unobserved exception handler: {0}", e.Exception.Message);
    };

    RunTask();

    int gcCounter = 0;
    while (true)
    {
        // artifical memory consumption
        int[][] a = new int[4096][];
        for (int j = 0; j < 4096; j++)
            a[j] = new int[4096];

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

        Console.WriteLine("Garbage collected: {0}", ++gcCounter);
        Thread.Sleep(500);
    }
}

static void RunTask()
{
    var faultedTask = Task.Factory.StartNew(() => { throw new Exception("Task is faulted"); });

    // 1. ContinueWhenAll hides unobserved exception
    Task.Factory.ContinueWhenAll(new[] { faultedTask }, ts =>
    {
        Console.WriteLine("From \"when all\" continuation");
    });

    // 2. Simple continuation does not hide unobserved exception
    //faultedTask.ContinueWith(t => { Console.WriteLine("From \"simple\" continuation"); });
}

所以,您没有得到该异常的原因仅仅是 Task 还没有被 GC。

【讨论】:

  • 刚刚测试过,这是因为 OP(和我自己)没有在发布模式下运行。您不需要单独的方法。只需在发布模式下运行就足够了。
  • @SriramSakthivel 确实如此。在发布模式下,两者都会引发事件。无论如何,它始终与ContinueWith 一致
  • 谢谢。是的,你是对的。没有引发事件的唯一原因是任务没有被 GC 处理。我认为 TPL 中有一些特殊行为,用于为调试和/或在调试器下编译的程序集。我在反汇编的 TPL 代码中看到了类似的东西,但我现在并不完全理解。可能与VS中“任务”窗口的支持有关。
  • @AntonK TPL 已经编译。不同之处在于您的代码是在发布或调试中编译的。但是,是的,它可能用于调试窗口。
猜你喜欢
  • 1970-01-01
  • 2016-11-25
  • 2012-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
  • 2011-12-14
相关资源
最近更新 更多