C# 捕获聚合异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19266338/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:32:49  来源:igfitidea点击:

Catching AggregateException

c#task-parallel-library

提问by MaPi

I am trying to throw and catch an AggregateException. I did not use exceptions very much on C#, but the behaviour I found is a little bit surprising.

我正在尝试抛出并捕获 AggregateException。我在 C# 上并没有经常使用异常,但我发现的行为有点令人惊讶。

My code is:

我的代码是:

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());
}

It is calling the function IsEven

它正在调用函数 IsEven

private static bool IsEven(int i)
{
    if (i % 10 == 0)
        throw new AggregateException("i");
    return i % 2 == 0;
}

That throws the AggregateException.

这会引发 AggregateException。

I would expect the code to write every even number in the 0,20 range and "There was 1 exceptions" twice.

我希望代码写入 0,20 范围内的每个偶数,并且“有 1 个例外”两次。

What I get is some numbers printed (they are random cause of ForAll) and then the exception is thrown, but not catched and the programs stop.

我得到的是打印的一些数字(它们是 ForAll 的随机原因),然后抛出异常,但没有被捕获并且程序停止。

Am i missing something?

我错过了什么吗?

采纳答案by Brian Reischl

This is actually kind of interesting. I think the problem is that you're using AggregateExceptionin an unexpected way, which is causing an error inside the PLINQ code.

这其实有点意思。我认为问题在于您AggregateException以一种意想不到的方式使用,这导致了 PLINQ 代码中的错误。

The entire point of AggregateExceptionis to group together multiple exceptions that may occur simultaneously (or nearly so) in a parallel process. So AggregateExceptionis expected to have at least one inner exception. But you're throwing new AggregateException("i"), which has no inner exceptions. The PLINQ code tries to examine the InnerExceptionsproperty, hits some sort of error (probably a NullPointerException) and then it seems to go into a loop of some sort. This is arguably a bug in PLINQ, since you're using a valid constructor for AggregateException, even if it is an unusual one.

重点AggregateException是将在并行过程中可能同时(或几乎同时)发生的多个异常组合在一起。所以AggregateException预计至少会有一个内部异常。但是你正在抛出new AggregateException("i"),它没有内部异常。PLINQ 代码尝试检查InnerExceptions属性,遇到某种错误(可能是 a NullPointerException),然后它似乎进入某种循环。这可以说是 PLINQ 中的一个错误,因为您使用了有效的构造函数 for AggregateException,即使它是一个不寻常的构造函数。

As pointed out elsewhere, throwing ArgumentExceptionwould be more semantically correct. But you can get the behavior you're looking for by throwing a correctly-constructed AggregateException, for example by changing the IsEvenfunction to something like this:

正如其他地方所指出的,投掷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;
}

I think the moral of the story is to not throw AggregateExceptionunless you really know exactly what you're doing, particularly if you're already inside a parallel or Task-based operation of some kind.

我认为这个故事的寓意是AggregateException除非你真的知道你在做什么,否则不要抛出,特别是如果你已经在某种并行或Task基于操作的操作中。

回答by svick

I agree with others: this is a bug in .Net and you should report it.

我同意其他人的看法:这是 .Net 中的一个错误,您应该报告它

The cause is in the method QueryEnd()in the internal class QueryTaskGroupState. Its decompiled (and slightly modified for clarity) code looks like this:

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

Basically, what this does is:

基本上,它的作用是:

  • rethrow the flattened AggregateExceptionif it contains any non-cancellation exceptions
  • throw new cancellation exception if cancellation was requested (or return without throwing, I don't really understand that part, but I don't think it's relevant here)
  • else throw ObjectDisposedExceptionfor some reason (assuming userInitiatedDisposeis false, which it is)
  • AggregateException如果它包含任何非取消异常,则重新抛出扁平化
  • 如果请求取消,则抛出新的取消异常(或者不抛出就返回,我不太了解那部分,但我认为这与此处无关)
  • elseObjectDisposedException出于某种原因抛出(假设userInitiatedDisposefalse,它是)

So, if you throw an AggregateExceptionwith no inner exceptions, exwill be an AggregateExceptioncontaining your empty AggregateExcaption. Calling Flatten()will turn that into just an empty AggreateException, which means it doesn't contain any non-cancellation exception, so the first part of the code thinks this is cancellation and doesn't throw.

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

But the second part of the code realizes this isn't cancellation, so it throws a completely bogus exception.

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