【问题标题】:.Net how to report all exceptions that are thrown during Task.WhenAll()?.Net 如何报告在 Task.WhenAll() 期间引发的所有异常?
【发布时间】:2016-07-21 18:29:35
【问题描述】:

我有一个表单应用程序,它在几个点上执行一个

await Task.WhenAll(list_of_tasks)

我的理解是,在这种情况下,如果 list_of_tasks 中的一个或多个任务抛出异常,“等待”也将只传播其中一个异常。 (但是,您可以检查每个任务以了解其失败的原因)

我的问题是:如果您有一个应用程序范围的异常处理程序,它将所有异常记录到日志中,它如何记录 list_of_tasks 中抛出的每个异常的信息? (假设异常处理程序只会显示从 await 发出的一个异常,并且该异常不像 AggregateException 那样告诉您其他异常)

迈克尔

【问题讨论】:

    标签: .net exception-handling async-await


    【解决方案1】:

    所以您可能已经知道,为了方便起见,await 解包 AggregateException,因为 Task.Exception 属性属于 AggregateException 类型,如果它没有解包 - 您必须在等待时始终捕获 AggregateException一些任务,然后费心解开自己。

    对于 Task.WhenAll,我认为这种行为确实不是你想要的。所有其他异常都被吞下(例如,被认为是观察到的,不会到达 UnobservedTaskException 处理程序)。如果在这种情况下记录所有异常对您很重要,您可以编写一些扩展方法,如下所示:

    static class TaskExtensions {
        static async Task WhenAllAggregate(params Task[] tasks) {
            var all = Task.WhenAll(tasks);
            try {
                await all;
            }
            catch {
                // swallow caugth exception but throw all               
                throw all.Exception;
            }
        }
    }
    

    然后像往常一样在应用程序范围的处理程序中记录聚合异常。

    【讨论】:

    • throwing all.Exception 时,不会重置堆栈跟踪(就像使用throw ex;throw; 时一样)?
    • @joerage all.Exception 是包含所有内部异常的 AggregateException,每个异常都有自己的堆栈跟踪。当您执行await all 时 - 它会选择第一个异常并重新抛出它(基本上是throw all.Exception.InnerExceptions[0])。我们只是将它们全部重新扔掉,所以这里不会丢失任何有价值的东西。
    【解决方案2】:

    Jon Skeet 提供a sketch for a solution 作为扩展方法:

    public static Task PreserveMultipleExceptions(this Task originalTask)
    {
        var tcs = new TaskCompletionSource<object>();
        originalTask.ContinueWith(task => {
            switch (task.Status) {
                case TaskStatus.Canceled:
                    tcs.SetCanceled();
                    break;
                case TaskStatus.RanToCompletion:
                    tcs.SetResult(null);
                    break;
                case TaskStatus.Faulted:
                    tcs.SetException(originalTask.Exception);
                    break;
            }
        }, TaskContinuationOptions.ExecuteSynchronously);
        return tcs.Task;
    }
    

    你可以这样使用:

    var whenAllTask = Task.WhenAll(tasks).PreserveMultipleExceptions();
    await whenAllTask; // Note that the exception isn't thrown until the task has been awaited!
    

    或者如果您更喜欢(在当前缺乏类型扩展方法的情况下):

    public static Task WhenAllPreserveMultipleExceptions(this IEnumerable<Task> tasks)
    {
        var aggregateTask = Task.WhenAll(tasks);
        var tcs = new TaskCompletionSource<object>();
        aggregateTask.ContinueWith(task => {
            switch (task.Status)
            {
                case TaskStatus.Canceled:
                    tcs.SetCanceled();
                    break;
                case TaskStatus.RanToCompletion:
                    tcs.SetResult(null);
                    break;
                case TaskStatus.Faulted:
                    tcs.SetException(aggregateTask.Exception);
                    break;
            }
        }, TaskContinuationOptions. ExecuteSynchronously);
        return tcs.Task;
    }
    

    你可以这样使用:

    var whenAllTask = tasks.WhenAllPreserveMultipleExceptions();
    await whenAllTask; // Note that the exception isn't thrown until the task has been awaited!
    

    currently accepted solution 的一个可能问题是它会在调用方法时抛出,而不是在等待它时抛出,这可能不是您想要的,并且不模仿 Task.WhenAll。)

    【讨论】:

      【解决方案3】:

      https://stackoverflow.com/a/36365489/340035的回答非常好。

      我唯一要改变的是字符数量并使用以下内容:

      等待Task.WhenAll(tasks).ContinueWith(t => t.Wait());

      您可以在这篇好文章中找到有关异常处理决策的更多信息: https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-14
        • 2019-12-02
        • 1970-01-01
        • 1970-01-01
        • 2021-08-16
        • 1970-01-01
        • 2023-04-02
        • 1970-01-01
        相关资源
        最近更新 更多