一个任务失败时,是否可以从Task.WhenAll获得成功的结果? [英] Is it possible to get successful results from a Task.WhenAll when one of the tasks fails?

查看:99
本文介绍了一个任务失败时,是否可以从Task.WhenAll获得成功的结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出以下内容:

var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));

var task = Task.WhenAll(tPass1, tFail1, tFail2);
task.Wait();

对task.Wait()的调用将抛出一个AggregateException,其内部异常包含fail1fail2异常.但是如何获得tPass1成功的结果?

the call to task.Wait() throws an AggregateException, whose inner exceptions contain the fail1 and fail2 exceptions. But how can I access the tPass1 successful result?

这可能吗?

我知道在WhenAll完成之后我可以通过tPass1.Result从单个任务中获得结果,但是有一种方法可以将它们放入数组中,从而不必手动跟踪所有进给的东西进入WhenAll?

I'm aware that I can get the result from the individual task after the WhenAll has finished, via tPass1.Result however is there a way to get them in an array to avoid having to manually track all the things feeding into the WhenAll?

推荐答案

任务失败时,我们将无法访问其 Result 属性,因为它会抛出.因此,要获得部分成功完成的WhenAll任务的结果,我们必须确保该任务将成功完成.然后,问题就变成了如何处理失败的内部任务.吞下它们可能不是一个好主意.至少我们要记录它们.这是一个替代性WhenAll的实现,该替代性不会抛出任何异常,但是会在 ValueTuple 结构.

When a task fails we cannot access its Result property because it throws. So to have the results of a partially successful WhenAll task, we must ensure that the task will complete successfully. The problem then becomes what to do with the exceptions of the failed internal tasks. Swallowing them is probably not a good idea. At least we would like to log them. Here is an implementation of an alternative WhenAll that never throws, but returns both the results and the exceptions in a ValueTuple struct.

public static Task<(T[] Results, Exception[] Exceptions)> WhenAllEx<T>(params Task<T>[] tasks)
{
    return Task.WhenAll(tasks).ContinueWith(_ => // return a continuation of WhenAll
    {
        var results = tasks
            .Where(t => t.Status == TaskStatus.RanToCompletion)
            .Select(t => t.Result)
            .ToArray();
        var aggregateExceptions = tasks
            .Where(t => t.IsFaulted)
            .Select(t => t.Exception) // The Exception is of type AggregateException
            .ToArray();
        var exceptions = new AggregateException(aggregateExceptions).Flatten()
            .InnerExceptions.ToArray(); // Trick to flatten the hierarchy of AggregateExceptions
        return (results, exceptions);
    }, TaskContinuationOptions.ExecuteSynchronously);
}

用法示例:

var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));

var task = WhenAllEx(tPass1, tFail1, tFail2);
task.Wait();
Console.WriteLine($"Status: {task.Status}");
Console.WriteLine($"Results: {String.Join(", ", task.Result.Results)}");
Console.WriteLine($"Exceptions: {String.Join(", ", task.Result.Exceptions.Select(ex => ex.Message))}");

输出:

状态:RanToCompletion
结果:1
例外:fail1,fail2

Status: RanToCompletion
Results: 1
Exceptions: fail1, fail2


警告:上面的实现会忽略已取消的任务,因此不会模仿


Caution: the implementation above ignores canceled tasks, and so does not mimic the default behavior of Task.WhenAll:

如果提供的任务均未故障,但至少其中一项被取消,则返回的任务将以Canceled状态结束.

在这种情况下,更一致的行为是返回单个TaskCanceledException.可以通过在WhenAllEx内的最后return命令之前添加以下代码来完成此操作:

A more consistent behavior would be to return a single TaskCanceledException in this case. This can be done by adding this code inside the WhenAllEx, just before the final return command:

if (exceptions.Length == 0)
{
    var canceledTask = tasks.FirstOrDefault(t => t.IsCanceled);
    if (canceledTask != null)
    {
        exceptions = new[] { new TaskCanceledException(canceledTask) };
    }
}

这篇关于一个任务失败时,是否可以从Task.WhenAll获得成功的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆