处理 C# AggregateException 的正确方法

Proper way to handle C# AggregateException

我有一个问题,关于在使用 WhenAll() 时何时可以安全地处理聚合异常。看起来自然的位置应该在 catch 块内,因为如果 catch 块从不触发,就意味着没有异常需要处理。但是我看到很多代码都有一个空的 catch 块,并在处理任何发现的异常之前检查 AggregateException 是否存在(包括在 MS 网站上)。


    public async Task MyMethod() {

      var tasks = new List<Task>();
      for (var i = 0; i < 10; i++) {
        tasks.Add(DoSthAsync());
      }

      var masterTask = Task.WhenAll(tasks);
      try {
        var results = await masterTask;
      } catch {
        // Safe to access masterTask here and handle aggregate exceptions? Have all tasks completed?
        foreach (var ex in masterTask.Exception.innerExceptions) {
          HandleException(ex);
        }
      }

      // Or necessary to check for and handle aggregate exceptions here?
      if (masterTask.Exception != null) {
        foreach (var ex in masterTask.Exception.innerExceptions) {
          HandleException(ex);
        }
      }
    }

    public async Task DoSthAsync() {
      // ...
    }

It seems like the natural place would be inside the catch block

是的,那会很好。 Task.WhenAll returns 当所有任务都完成时完成的任务。所以在你的情况下,当你的代码进入 catch 块时,masterTask 已经完成,这意味着所有 tasks 已经完成。

您发布的代码有效,因为 Task.WhenAll returns 一个只有在所有子任务都完成后才能完成的任务。

为什么代码会那样做?为什么没有 catch (Exception ex)?这是因为 await 只抛出第一个内部异常。如果您需要访问多个异常,此代码模式是实现此目的的好方法。您也可以执行 catch (AggregateException ex) 并使用该对象。是同一个对象。


我个人避免那样使用 catch。本质上,它使用异常来控制流程。这使得调试更加困难,并可能导致冗长的代码。

我喜欢这个:

var whenAllTask = Task.WhenAll(...);

await whenAllTask.ContinueWith(_ => { }); //never throws

if (whenAllTask.Exception != null) ... //handle exceptions

我把 .ContinueWith(_ => { }) 位变成了 WhenCompleted 扩展方法,这样代码看起来很干净。


然后你想知道第二种检查异常的方法是否是个好主意:

  // Or necessary to check for and handle aggregate exceptions here?
  if (masterTask.Exception != null) {
    foreach (var ex in masterTask.Exception.innerExceptions) {
      HandleException(ex);
    }
  }

你当然可以做到。本质上是一样的。在特定情况下使用更方便的。