【问题标题】:How to handle exceptions when using Task Parallel Library Task.WhenAny()使用任务并行库 Task.WhenAny() 时如何处理异常
【发布时间】:2015-08-04 20:22:18
【问题描述】:

当我使用Task.WhenAll() 函数并在任务中引发异常时,会引发新的 AggregateException,我可以捕获它以查看任务中发生的所有异常。但是,当我使用 Task.WhenAny() 时,不会引发异常。相反,我必须检查Task.Exception 属性的值,以查看是否发生异常。这似乎是一种糟糕的代码气味,因为我必须记住每次使用Task.WhenAny() 时都要检查Task.Exception 属性。不应该有更好的方法吗?

这是我的意思的一个例子:

private async void btnMultipleExceptions_Click(object sender, EventArgs e) {
        var task1 = ThrowNotImplementedException();
        var task2 = ThrowDivideByZeroException();

        try {
            Task task = await Task.WhenAny(task1, task2);

            // Even if an exception is thrown in one of the tasks (in our case,
            // task1 will throw first) no exception is thrown from
            // the above await Task.WhenAny(). Instead, the exception is placed on the 
            // Task.Exception property. So I need to check for it every time 
            // I call Task.WhenAny()?
            if (task.Exception != null) {
                Console.WriteLine("Exceptions: " + string.Join(Environment.NewLine,
                    task.Exception.InnerExceptions.Select(x => x.Message).ToArray()));
            } else {
                Console.WriteLine("No Exceptions!");
            }
        } catch(Exception ex) {
            // Try to catch all exceptions???
            AggregateException allEx = ex as AggregateException;

            if (allEx != null) {
                Console.WriteLine("Exceptions: " + string.Join(Environment.NewLine,
                    allEx.InnerExceptions.Select(x => x.Message).ToArray()));
            } else {
                Console.WriteLine("Exceptions: " + ex.Message);
            }
        }
    }

    private async Task ThrowNotImplementedException() {
        await Task.Delay(TimeSpan.FromSeconds(1));
        throw new NotImplementedException();
    }

    private async Task ThrowDivideByZeroException() {
        await Task.Delay(TimeSpan.FromSeconds(2));
        throw new DivideByZeroException();
    }

【问题讨论】:

    标签: c# .net async-await task-parallel-library c#-5.0


    【解决方案1】:

    只需awaitWhenAny 返回的任务。如果它出错了,它会解开异常并抛出它,如果没有,你知道它已经完成了,所以你可以继续。

    或者,您可以简单地在Task.WhenAny 上调用Unwrap,然后再调用await。这在语义上与前一个选项相同;唯一的区别在于您是否认为这或多或少清楚。

    如果您发现自己非常频繁地解包 WhenAny 的结果,您可以简单地编写自己的实现来为您解包任务,然后使用它们来代替:

    public static Task WhenAny(IEnumerable<Task> tasks)
    {
        return Task.WhenAny(tasks).Unwrap();
    }
    public static Task<T> WhenAny<T>(IEnumerable<Task<T>> tasks)
    {
        return Task.WhenAny(tasks).Unwrap();
    }
    //TODO add wrappers for the `params` overloads if you want them too
    

    【讨论】:

    • 感谢您的扩展。不幸的是,我认为我仍然缺少一些明显的东西。如果我使用await Task.WhenAny(task1, task2); 并且下一行是Console.WriteLine("No Exceptions");,则Console.WriteLine(); 将被执行。捕获处理程序不是。使用Task.WhenAny()时我还有什么需要做的吗?
    • @HenryDaehnke 当你说await Task.WhenAny(task1, task2); 时,它会解析为Task,因为WhenAny 返回一个Task&lt;Task&gt;,所以等待它会给你一个Task。如果你await再次(或在等待它之前打开它)那么你正在等待出错的实际任务,并且执行将转到catch块。这就是答案的重点,您需要等待内部任务,而不是外部任务。
    • 好吧,这真的很奇怪。显然,Task.WhenAny() doesn't 在您 await 时重新抛出异常。相反,您可以await await(即await await Task.WhenAny(task1, task2))。从link 得到答案。
    • 开枪!不好意思,刚看到你的评论。是的,现在说得通了。我真的不明白你的第一个答案。我会将问题标记为已回答。再次感谢!
    【解决方案2】:

    虽然接受的答案会给你一个例外首先完成的任务。第二个任务中的异常仍然会被抛出,并且在 4.5 之前的 .NET 版本中,如果它未被观察到,它将在线程级别升级,当它被 GC 时。观察和记录非致命任务异常是 Continuations 的绝佳用途(我从您的 WhenAny() 场景中假设,如果您的一项任务失败,这不是致命的情况)。考虑这样的事情:

    private static void LogIfErrors(Task source)
    {
        if(source.Exception == null) return;
        source.Exception.Handle(ex =>
        {
            Log.Error("#unhandled #task #error", ex);
            return true;
        });
        return;
    }
    
    private void DoStuff()
    {
        // note that you cannot inline the ContinueWith() statement,
        // because it would change the value of task1 to hold your
        // continuation instead of your parent task
    
        var task1 = ThrowNotImplementedException();
        task1.ContinueWith(LogIfErrors, TaskContinuationOptions.OnlyOnFaulted);
    
        var task2 = ThrowDivideByZeroException();
        task2.ContinueWith(LogIfErrors, TaskContinuationOptions.OnlyOnFaulted);
    
        var firstCompleted = await Tasks.WhenAny(task1, task2).Unwrap();
    
    }
    

    现在,即使您的一个任务长时间运行并且在您的第一个完成任务被WhenAny() 返回很久之后抛出异常,您仍然有机会观察异常并且(在

    【讨论】:

    • 从 .NET 4.5 开始,这一切都变得毫无意义,其中忽略了未观察到的异常,而不是拆除线程。
    • 好点,为了清楚起见进行了修改。即使在我的 4.5 应用程序中,我仍然非常虔诚地使用此模型,以便即发即弃任务的异常会显示在我的日志中!
    • @Dusty 甚至不知道未观察到的异常的潜在问题。即使他们不再拆除 .NET 4.5 中的线程,这也是记录它们的一个好点。
    【解决方案3】:

    等待等待

    await await Task.WhenAny(...);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多