我正在尝试抛出并捕获AggregateException。 我没有在C#上使用异常,但我发现的行为有点令人惊讶。
我的代码是:
var numbers = Enumerable.Range(0, 20);
try
{
var parallelResult = numbers.AsParallel()
.Where(i => IsEven(i));
parallelResult.ForAll(e => Console.WriteLine(e));
}
catch (AggregateException e)
{
Console.WriteLine("There was {0} exceptions", e.InnerExceptions.Count());
}
它正在调用函数IsEven
private static bool IsEven(int i)
{
if (i % 10 == 0)
throw new AggregateException("i");
return i % 2 == 0;
}
抛出AggregateException。
我希望代码能够写出0,20范围内的每个偶数和“有1个例外”两次。
我得到的是打印的一些数字(它们是ForAll的随机原因)然后抛出异常,但没有抓住并且程序停止。
我错过了什么吗?
答案 0 :(得分:22)
这实际上很有趣。我认为问题是你以一种意想不到的方式使用AggregateException
,这导致PLINQ代码内部出错。
AggregateException
的重点是将并行进程中可能同时(或几乎如此)发生的多个异常组合在一起。所以AggregateException
预计会有至少一个内部异常。但是你正在抛出new AggregateException("i")
,它没有内在的例外。 PLINQ代码尝试检查InnerExceptions
属性,遇到某种错误(可能是NullPointerException
)然后它似乎进入某种循环。这可以说是PLINQ中的一个错误,因为你正在使用AggregateException
的有效构造函数,即使它是一个不常见的构造函数。
正如其他地方所指出的,抛出ArgumentException
在语义上会更正确。但是你可以通过抛出一个正确构造的AggregateException
来获得你正在寻找的行为,例如通过将IsEven
函数更改为这样的函数:
private static bool IsEven(int i)
{
if (i % 10 == 0){
//This is still weird
//You shouldn't do this. Just throw the ArgumentException.
throw new AggregateException(new ArgumentException("I hate multiples of 10"));
}
return i % 2 == 0;
}
我认为这个故事的寓意是不要抛出AggregateException
,除非你确切地知道你在做什么,特别是如果你已经在某个并行或基于Task
的操作中样。
答案 1 :(得分:7)
我同意其他人的观点:这是.Net中的一个错误,你应该report it。
原因在于内部类QueryEnd()
中的方法QueryTaskGroupState
。
它的反编译(为清晰起见略微修改)代码如下所示:
try
{
this.m_rootTask.Wait();
}
catch (AggregateException ex)
{
AggregateException aggregateException = ex.Flatten();
bool cacellation = true;
for (int i = 0; i < aggregateException.InnerExceptions.Count; ++i)
{
var canceledException =
aggregateException.InnerExceptions[i] as OperationCanceledException;
if (IsCancellation(canceledException))
{
cacellation = false;
break;
}
}
if (!cacellation)
throw aggregateException;
}
finally
{
this.m_rootTask.Dispose();
}
if (!this.m_cancellationState.MergedCancellationToken.IsCancellationRequested)
return;
if (!this.m_cancellationState.TopLevelDisposedFlag.Value)
CancellationState.ThrowWithStandardMessageIfCanceled(
this.m_cancellationState.ExternalCancellationToken);
if (!userInitiatedDispose)
throw new ObjectDisposedException(
"enumerator", "The query enumerator has been disposed.");
基本上,它的作用是:
AggregateException
ObjectDisposedException
(假设userInitiatedDispose
为false
,它就是这样)因此,如果您抛出AggregateException
没有内部异常,ex
将是AggregateException
,其中包含您的空AggregateExcaption
。调用Flatten()
会将其转换为空AggreateException
,这意味着它不包含任何非取消异常,因此代码的第一部分认为这是取消并且不会抛出。 / p>
但是代码的第二部分意识到这不是取消,所以它抛出了一个完全虚假的异常。