聚合异常不包含所有任务异常?

时间:2011-08-17 15:24:40

标签: c# exception console-application task-parallel-library

我目前正在创建一个演示项目,向我的团队的其他成员展示他们如何使用TPL来制作更好的代码。但是,我对一个我认为应该以另一种方式运作的问题感到难过;代码:

// Example 7 - Even more exceptions

try
{
    var numberList = Enumerable.Range(0, 1000).AsParallel().Select(x =>
        {
            if (x % 2 == 0)
                throw new ApplicationException("Shazam!");

            return x;
        }).ToList();
}
catch (AggregateException e)
{
    int exceptionsAggregated = 0;
    e.Flatten().Handle(ex =>
        {
            if (ex is ApplicationException)
            {
                if ((ex as ApplicationException).Message == "Shazam!")
                    exceptionsAggregated++;
            }

            return true;
        });

    Console.WriteLine("Exceptions: " + exceptionsAggregated);
}

除了要发生的事情之外,聚合异常将包含500个内部异常,因为在PLINQ中调用的每个其他线程都会抛出异常。但是,我在Aggregate异常中只获得了4个异常。

我的第一个问题是,当TPL达到可以抛出的异常数量限制时,TPL可能会终止运行。但是,我似乎无法找到任何支持该声明的在线文章或文档。所以我有点难过;什么会导致只包含4个例外?

我只是错过了一些基本的东西吗?

编辑:@Dan Bryant下面把它钉在头上;当我将代码更改为以下内容时:

//示例7 - 更多例外

try
{
    var tasks = Enumerable.Range(0, 100).Select(x => Task.Factory.StartNew(() =>
        {
            if (x % 2 == 0)
                throw new ApplicationException("Shazam!");

            return x;
        })).ToArray();

    Task.WaitAll(tasks);
}
catch (AggregateException e)
{
    int exceptionsAggregated = 0;
    e.Flatten().Handle(ex =>
        {
            if (ex is ApplicationException)
            {
                if ((ex as ApplicationException).Message == "Shazam!")
                    exceptionsAggregated++;
            }

            return true;
        });

    Console.WriteLine("Exceptions: " + exceptionsAggregated);
}

我正确地获得了正确的例外数量。问题解决了!

2 个答案:

答案 0 :(得分:1)

一种可能性是并行Select正在创建父任务,每次迭代都有一个子任务。默认的TaskScheduler只会同时执行一定数量的任务;如果这些子任务中的任何一个失败,则父任务将失败,这意味着尚未执行的子任务将不会执行。

这与Select不应具有副作用的概念一致,因为Select中间的失败将阻止后续枚举调用停止执行。与Parallel版本的不同之处在于,您可以发生一些异常(由于部分并行执行),而'serial'Select只能枚举一个异常。

另一种可能性是它创建了固定数量的任务,然后通过并发阻塞集合为它们分配工作。每个任务失败后,它将停止执行其分配的工作负载。我认为后一种解释实际上更有可能,但我必须深入了解实施情况。

答案 1 :(得分:1)

一旦发现异常,任务就会停止计划任务。它返回到目前为止的异常。允许完成现有任务(如果可以),并且只获得实际运行的任务的异常。任何仍在等待运行的任务都无法启动。 (请记住,任务不一定立即开始)

以下是几个月前我在博客中提供的更多信息:http://colinmackay.co.uk/blog/2011/02/14/parallelisation-in-net-40-part-2-throwing-exceptions/

因为您正在使用PLINQ,所以您还应该知道,在您调用调用WaitAll的内容之前,异常不会冒泡到调用线程。更多信息: http://colinmackay.co.uk/blog/2011/05/16/tasks-that-throw-exceptions/