【问题标题】:Catching AggregateException捕获聚合异常
【发布时间】:2013-10-09 08:05:28
【问题描述】:

我正在尝试抛出并捕获 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 的随机原因),然后抛出异常,但没有被捕获并且程序停止。

我错过了什么吗?

【问题讨论】:

  • 不知道为什么会这样,尝试将 throw new AggregateException("i"); 更改为 throw new ArgumentException("i"); 会产生预期的结果
  • 您通过抛出错误的异常来混淆系统。改为抛出 Argument 或 InvalidOperationException。
  • @SriramSakthivel - 为什么您的版本会导致应用程序崩溃 - 我必须推测,但它的 InnerExceptions 将为空。出乎意料。
  • @SriramSakthivel - 但内部代码不应抛出聚合。不是他们的工作。
  • @HenkHolterman 当然,我知道这是一个坏主意,AggregateException 的目的是结合许多异常并保留 StackTrace。但我找不到任何说明我们不应该这样做的文档

标签: c# task-parallel-library


【解决方案1】:

这其实有点意思。我认为问题在于您以一种意想不到的方式使用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 的某种操作中。

【讨论】:

  • 请编辑以便您拥有正确版本的代码(抛出 ArgumentException 而不是抛出 AggregateException)。您最后确实说过,但恐怕有人会一看并认为发布的代码是正确的(不是)。
  • 抛出一个你不应该像我一样做的评论。
【解决方案2】:

我同意其他人的观点:这是 .Net 中的一个错误,您应该 report it

原因在于内部类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.");

基本上,它的作用是:

  • 如果扁平化的AggregateException 包含任何不可取消的异常,则重新抛出它
  • 如果请求取消,则抛出新的取消异常(或不抛出就返回,我不太了解那部分,但我认为这里不相关)
  • 否则出于某种原因抛出ObjectDisposedException(假设userInitiatedDisposefalse,就是这样)

所以,如果你抛出一个没有内部异常的AggregateExceptionex 将是一个包含你空的AggregateExcaptionAggregateException。调用Flatten() 将把它变成一个空的AggreateException,这意味着它不包含任何非取消异常,所以代码的第一部分认为这是取消并且不会抛出。

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2014-02-17
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 2011-09-07
  • 1970-01-01
  • 2010-09-28
相关资源
最近更新 更多