【问题标题】:How to better understand the code/statements from "Async - Handling multiple Exceptions" article?如何更好地理解“异步 - 处理多个异常”文章中的代码/语句?
【发布时间】:2025-12-13 20:30:02
【问题描述】:

运行以下 C# 控制台应用程序

class Program
{  static void Main(string[] args)
   {  Tst();
      Console.ReadLine();
   }
   async static Task  Tst()
   {
       try
       {
           await Task.Factory.StartNew
             (() =>
                {
                   Task.Factory.StartNew
                       (() =>
                         { throw new NullReferenceException(); }
                         , TaskCreationOptions.AttachedToParent
                        );
               Task.Factory.StartNew
                       (  () =>
                               { throw new ArgumentException(); }
                               ,TaskCreationOptions.AttachedToParent
                       );
                }
             );
    }
    catch (AggregateException ex)
    {
        // this catch will never be target
        Console.WriteLine("** {0} **", ex.GetType().Name);

//******  Update1 - Start of Added code
        foreach (var exc in ex.Flatten().InnerExceptions)
        {
             Console.WriteLine(exc.GetType().Name);
        }
//******  Update1 - End of Added code
    }
    catch (Exception ex)
    {
       Console.WriteLine("## {0} ##", ex.GetType().Name);
    }
 }

产生输出:

** AggregateException **

不过,上面的代码是从"Async - Handling multiple Exceptions" 博客文章中复制第一个sn-p,它讲述了它:

以下代码将捕获单个 NullReferenceException 或 ArgumentException 异常(AggregateException 将被忽略)

问题出在哪里:

  1. 文章有误?
    哪些代码/语句以及如何更改以正确理解它?
  2. 我在复制文章的第一个代码sn-p时出错了?
  3. 这是由于 .NET 4.0/VS2010 Async CTP 扩展中的一个错误,我正在使用?

Update1(响应svick's answer

添加代码后

//******  Update1 - Start of Added code
        foreach (var exc in ex.Flatten().InnerExceptions)
        {
             Console.WriteLine(exc.GetType().Name);
        }
//******  Update1 - End of Added code

产生的输出是:

** AggregateException **
NullReferenceException

所以,commented Matt Smith

被捕获的AggregateException 仅包含其中一个 抛出的异常(NullReferenceExceptionArgumentException 取决于孩子的执行顺序 任务)

很可能,这篇文章仍然正确,或者至少,非常有用。我只需要了解如何更好地阅读/理解/使用它

Update2(响应svick's answer

执行svick的代码:

internal class Program
{
  private static void Main(string[] args)
  {
    Tst();
    Console.ReadLine();
  }

  private static async Task Tst()
  {
    try
    {
      await TaskEx.WhenAll
        (
          Task.Factory.StartNew
            (() =>
               { throw new NullReferenceException(); }
            //, TaskCreationOptions.AttachedToParent
            ),
          Task.Factory.StartNew
            (() =>
               { throw new ArgumentException(); }
            //,TaskCreationOptions.AttachedToParent
            )

        );
    }
    catch (AggregateException ex)
    {
      // this catch will never be target
      Console.WriteLine("** {0} **", ex.GetType().Name);

      //******  Update1 - Start of Added code
      foreach (var exc in ex.Flatten().InnerExceptions)
      {
        Console.WriteLine("==="+exc.GetType().Name);
      }
      //******  Update1 - End of Added code
    }
    catch (Exception ex)
    {
      Console.WriteLine("## {0} ##", ex.GetType().Name);
    }
  }
}

产生:

## NullReferenceException ##

输出。

为什么AggregateException 没有被生产或捕获?

【问题讨论】:

  • 我在 VS2012 和 .Net4.5 上看到了相同的结果。我很确定这篇文章是错误的。使用AttachedToParent 意味着异常将被自动传播(即使没有awaitWait().Result 调用),但父Task 仍将抛出一个AggregateException,其内部异常对应于实际抛出的异常。
  • @dlev,谢谢。您可以将您的评论复制粘贴到答案部分吗?我正在考虑发布有关子任务和分离嵌套任务之间异常传播差异的问题
  • 注意,dlev 的评论有点误导:如果您使用WaitResult,您将得到包含所有异常的外部AggregateException。如果您执行await,您将获得内部AggregateExceptions 之一,它表示只有一个Tasks 引发的异常。
  • @MattSmith,如果你把你的 cmets 作为答案,我会很感激
  • #dlev,刚刚从我的休眠状态(睡眠)退出后检查 - 没有 await 没有捕获到异常(但 await 只捕获了 2 个中的 1 个)

标签: c# exception-handling task-parallel-library async-await async-ctp


【解决方案1】:

响应您的“更新 2”,推理仍然与 svick 的回答相同。该任务包含一个AggregateException,但等待它会抛出第一个InnerException

您需要的其他信息在 Task.WhenAll 文档中(重点是我的):

如果提供的任何任务在故障状态下完成,则返回的任务也将在故障状态下完成,其异常将包含来自每个提供的任务。

所以Task的异常看起来像:

AggregateException 
    NullReferenceException 
    ArgumentException

【讨论】:

  • 那么,为什么AggreagateException 永远不会被抓到呢?对于 Update2 的分离任务,异常是否真的包含在 AggreagateException 中? catch (AggregateException ex){} 中的代码从未输入过。该异常仅在第二个 catch (Exception ex) {} 块中被捕获(没有它,就会丢失)
  • 因为永远不会抛出 AggregateException。当任务最终处于故障状态时,在其上使用await 将执行以下操作: 1. 获取任务的AggregateException(它始终是AggregateException) 2. 取出第一个InnerException 并扔掉它。
【解决方案2】:

文章有误。当您运行代码时,awaited Task 包含一个如下所示的异常:

AggregateException
  AggregateException
    NullReferenceException
  AggregateException
    ArgumentException

await(或者,更具体地说,TaskAwaiter.GetResult())在这里所做的是它采用外部 AggregateException 并重新抛出其第一个子异常。在这里,这是另一个AggregateException,所以这就是抛出的内容。

Task 有多个异常并且其中一个在await 之后直接重新抛出的代码示例是使用Task.WhenAll() 而不是AttachedToParent

try
{
    await Task.WhenAll(
        Task.Factory.StartNew(() => { throw new NullReferenceException(); }),
        Task.Factory.StartNew(() => { throw new ArgumentException(); }));
}
catch (AggregateException ex)
{
    // this catch will never be target
    Console.WriteLine("** {0} **", ex.GetType().Name);
}
catch (Exception ex)
{
    Console.WriteLine("## {0} ##", ex.GetType().Name);
}

【讨论】:

  • +1,要突出显示:被捕获的 AggregateException 仅包含 一个 引发的异常(NullReferenceExceptionArgumentException 取决于按子执行顺序Tasks)
  • @svick ,您的起始陈述不正确(请参阅我的问题中的 Update1 )并且在我了解如何纠正错误或正确/更好地理解(消除歧义)不正确的陈述(以及它们为什么存在以及它们应该传达的内容)
  • @MattSmith,它独立于“子任务的执行顺序”,但我很难掌握why only one exception from child tasks always propagated?
  • @Геннадий-Ванин 您的更新与我的回答并不矛盾。就像我说的,await 抛出的异常不是外部AggregateException,而是内部异常中的第一个。因此,对只返回一个异常的捕获异常调用Flatten() 除外。但是如果将Task 保存到变量中并调用task.Exception.Flatten(),则会返回两个异常。