如何在使用Task Parallel Library Task.WhenAny()时处理异常

时间:2015-05-22 17:53:21

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

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

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

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();
    }

3 个答案:

答案 0 :(得分:2)

只需await WhenAnyUnwrap返回的任务。如果它出现故障,它将打开例外并扔掉它,如果它没有,你知道它已经完成了,所以你可以继续。

或者,您只需拨打Task.WhenAny上的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 的结果,您可以编写自己的实现来为您解开任务,并使用它们代替:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    if (application.applicationState == UIApplicationStateActive)
    {
        NSLog(@"active");

        // display some foreground notification;
        [application cancelLocalNotification:notification];
    }
    else
    {
        NSLog(@"inactive");
    }
}

答案 1 :(得分:2)

虽然接受的答案会从首先完成的任务中提供例外。第二个任务中的异常仍将被抛出,并且在4.5之前的.NET版本中,如果它未被观察到,它将在线程级别升级。观察和记录非致命任务异常是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()返回首次完成任务后很长时间内抛出异常,你仍然有机会观察和异常, (在&lt; = .NET 4.0中)阻止它杀死连接的线程。

答案 2 :(得分:1)

等待着等待

await await Task.WhenAny(...);