我想等待抛出AggregateException,而不仅仅是第一个Exception

usr*_*usr 16 c# task-parallel-library async-await c#-5.0

等待故障任务(具有异常集的任务)时,await将重新抛出存储的异常.如果存储的异常是a AggregateException,它将重新抛出第一个并丢弃其余的异常.

我们如何使用await并同时抛出原件AggregateException以便我们不会意外丢失错误信息?

注意,当然可以考虑使用hacky解决方案(例如,试一试await,然后调用Task.Wait).我真的希望找到一个干净的解决方案.这里最好的做法是什么?

我想过使用自定义awaiter,但内置TaskAwaiter包含很多魔法,我不知道如何完全重现.它调用TPL类型的内部API.我也不想重现所有这些.

如果你想玩它,这是一个简短的repro:

static void Main()
{
    Run().Wait();
}

static async Task Run()
{
    Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
    await Task.WhenAll(tasks);
}

static Task CreateTask(string message)
{
    return Task.Factory.StartNew(() => { throw new Exception(message); });
}
Run Code Online (Sandbox Code Playgroud)

只抛出两个例外中的一个Run.

请注意,Stack Overflow上的其他问题无法解决此特定问题.建议重复时请小心.

Ste*_*ary 23

我不同意你的问题标题中暗示await行为是不受欢迎的.在绝大多数场景中都有意义.在一个WhenAll情况下,你多久真的需要知道所有的错误的详细信息,而不是只有一个?

主要的困难AggregateException是异常处理,即你失去了捕获特定类型的能力.

也就是说,使用扩展方法很容易获得所需的行为:

public static async Task WithAggregateException(this Task source)
{
  try
  {
    await source.ConfigureAwait(false);
  }
  catch
  {
    // source.Exception may be null if the task was canceled.
    if (source.Exception == null)
      throw;

    // EDI preserves the original exception's stack trace, if any.
    ExceptionDispatchInfo.Capture(source.Exception).Throw();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 获取所有异常对于诊断非常重要.我不想错过nullrefs等等.必须记录它们.*处理*异常是一种不同的情况.顺便说一句,解决方案工作正常.谢谢. (9认同)
  • 如果您正在谈论[致命或无用的例外](http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx),我建议您使用顶级处理程序(`UnobservedTaskException`,应用程序处理程序),而不是重复的`try` /`catch`块。 (2认同)
  • 我大多同意。我想到的场景确实是一个顶级错误处理程序(它所做的只是日志崩溃)。但是完整的崩溃信息必须以某种方式到达那里。UnobservedTaskException 和 Application 处理程序不保证在 await 存在的情况下,因为错误可能最终被观察到但被吞掉。当我看到错误被吞下时,我会非常紧张。我见过太多生产连续一个月出现严重错误而没有人注意到的情况。 (2认同)
  • [`unobservedTaskException`仍然被提出](http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx)对于未观察到的异常(它只是不会使进程崩溃).你有一个被观察的吞下异常的唯一情况是`WhenAll`,它只在你有多个异常时发生,其中一个是*不被吞噬.你真的必须*尝试*忽略`await`的异常. (2认同)
  • @BatteryBackupUnit:Task有两个主要用途:一个用作异步代码的"未来",另一个用作并行代码的"工作单元".我认为你的方法至少需要一个mutable/freezable`AggregateException`.随意[发表您的想法进行讨论](https://github.com/dotnet/corefx).除此之外:从技术上讲,`await`不会打开`AggregateException`,但这是一个好的心理模型. (2认同)
  • 考虑到“ source.Exception”在取消期间可能为空引用。 (2认同)

The*_*ias 6

这是 Stephen ClearyWithAggregateException扩展方法的一个较短的实现:

public static async Task WithAggregateException(this Task source)
{
    try { await source.ConfigureAwait(false); }
    catch when (source.IsCanceled) { throw; }
    catch { source.Wait(); }
}

public static async Task<T> WithAggregateException<T>(this Task<T> source)
{
    try { return await source.ConfigureAwait(false); }
    catch when (source.IsCanceled) { throw; }
    catch { return source.Result; }
}
Run Code Online (Sandbox Code Playgroud)

此方法基于 Stephen Toub 在GitHub 的 API提案中的建议。


更新:我添加了对取消情况的特殊处理,以防止传播AggregateException包含OperationCanceledException. 现在OperationCanceledException直接传播,并且Task.IsCanceled状态被保留。感谢@noseratio 在这个答案的评论中指出了这个缺陷。当然,现在这个实现并不比 Stephen Cleary 的方法短多少!


ado*_*thy 5

我知道我迟到了,但我发现这个巧妙的小技巧可以满足您的需求。由于完整的异常集可用于等待的任务,因此调用此任务的 Wait 或 .Result 将引发聚合异常。

    static void Main(string[] args)
    {
        var task = Run();
        task.Wait();
    }
    public static async Task Run()
    {

        Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
        var compositeTask = Task.WhenAll(tasks);
        try
        {
            await compositeTask.ContinueWith((antecedant) => { }, TaskContinuationOptions.ExecuteSynchronously);
            compositeTask.Wait();
        }
        catch (AggregateException aex)
        {
            foreach (var ex in aex.InnerExceptions)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

    static Task CreateTask(string message)
    {
        return Task.Factory.StartNew(() => { throw new Exception(message); });
    }
Run Code Online (Sandbox Code Playgroud)