我目前正在创建一个演示项目,向我的团队的其他成员展示他们如何使用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);
}
我正确地获得了正确的例外数量。问题解决了!
答案 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/