捕获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 (假设userInitiatedDisposefalse ,它是)

因此,如果抛出没有内部exception的AggregateException ,则ex将是包含空AggregateExcaption 。 调用Flatten()会将其转换为空的AggreateException ,这意味着它不包含任何非取消exception,因此代码的第一部分认为这是取消而不抛出。

但是代码的第二部分意识到这不是取消,所以它抛出了一个完全虚假的exception。