捕获AggregateException
我试图抛出并捕获AggregateException。 我没有在C#上使用exception,但我发现的行为有点令人惊讶。
我的代码是:
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的随机原因)然后抛出exception,但没有捕获并且程序停止。
我错过了什么吗?
这实际上很有趣。 我认为问题是你以一种意想不到的方式使用AggregateException
,这导致PLINQ代码内部出错。
AggregateException
的整个要点是将可能在并行进程中同时(或几乎如此)发生的多个exception组合在一起。 所以AggregateException
应该至少有一个内部exception。 但是你抛出了new AggregateException("i")
,它没有内部exception。 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
的操作中。
我同意其他人:这是.Net中的一个错误,你应该报告它 。
原因在于内部类QueryTaskGroupState
中的方法QueryEnd()
。 它的反编译(为清晰起见略微修改)代码如下所示:
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.");
基本上,它的作用是:
- 如果包含任何非取消exception,则重新抛出展平的
AggregateException
- 如果要求取消,则抛出新的取消例外(或者在没有投掷的情况下返回,我真的不理解那部分,但我不认为这与此相关)
- 否则因某种原因抛出
ObjectDisposedException
(假设userInitiatedDispose
为false
,它是)
因此,如果抛出没有内部exception的AggregateException
,则ex
将是包含空AggregateExcaption
。 调用Flatten()
会将其转换为空的AggreateException
,这意味着它不包含任何非取消exception,因此代码的第一部分认为这是取消而不抛出。
但是代码的第二部分意识到这不是取消,所以它抛出了一个完全虚假的exception。