【问题标题】:Exception stack trace and aggregation in Parallel.ForEachParallel.ForEach 中的异常堆栈跟踪和聚合
【发布时间】:2016-06-01 16:57:35
【问题描述】:
我有一些代码要并行化。它目前看起来像这样:
foreach (var item in collection)
{
if (error)
{
throw new Exception();
}
}
异常及其堆栈跟踪保存在日志文件中。
并行化后,它看起来像这样:
Parallel.ForEach(collection, item =>
{
if (error)
{
throw new Exception();
}
});
- 此异常将如何停止其他项目的并行执行?一旦在一个循环线程中遇到错误,有没有办法停止所有其他执行?
- 这将如何影响异常堆栈跟踪?
【问题讨论】:
标签:
c#
multithreading
exception
parallel-processing
task-parallel-library
【解决方案1】:
当抛出异常时,将取消尚未计划的项目的执行。但是,已经安排好的项目不会也不能取消。 (请记住,这是并行发生的,这就是您首先使用Parallel 的原因。)它们将运行到最后,并且自己可能会抛出异常。
因此,并行循环中可能会引发多个异常。这就是为什么它们总是被包裹在AggregateException 中,即使异常只被抛出一次。您可以捕获 AggregateException 并通过其 InnerExceptions 属性进行枚举,该属性包含所有抛出的异常及其堆栈跟踪:
try
{
Parallel.ForEach(collection, item =>
{
if (error)
{
throw new Exception();
}
});
}
catch (AggregateException ex)
{
foreach (var exception in ex.InnerExceptions)
{
// do something
}
}
【解决方案2】:
以下代码可用于验证 Evk 答案第一部分中讨论的行为:
static void TryCatchFunction()
{
ConcurrentBag<string> bag = null;
int numItemsInBag = 0;
try
{
ErrorFunction(out bag);
numItemsInBag = bag.Count;
}
catch (Exception)
{
numItemsInBag = bag.Count;
}
}
static void ErrorFunction(out ConcurrentBag<string> bag)
{
string[] strings = new string[] { "1", "2", "3", "4", "5", "6" };
ConcurrentBag<string> inFunctionBag = new ConcurrentBag<string>();
bag = inFunctionBag;
Parallel.ForEach(strings, (str, state) =>
{
if (str == "2" || str == "4")
{
inFunctionBag.Add(str);
throw new Exception();
}
});
}
在双核机器上的方法调用之间,包中的物品数量会有所不同。发生这种情况是因为有时异常会取消另一个线程的执行,而其他时间则都运行直到完成。