当一项任务失败时,是否可以从Task.WhenAll获得成功的结果?

Ori*_*rds 2 c# asynchronous task

给定以下内容:

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();
Run Code Online (Sandbox Code Playgroud)

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

这可能吗?

我知道可以在完成后从单个任务中获取结果WhenAlltPass1.Result但是可以通过一种方法将它们放入数组中,而不必手动跟踪送入的所有内容WhenAll

Fab*_*bio 5

也许

public async Task<Task[]> RejectFailedFrom(params Task[] tasks)
{
    try
    {
        await Task.WhenAll(tasks);
    }
    catch(Exception exception)
    {
        // Handle failed tasks maybe
    }

    return tasks.Where(task => task.Status == TaskStatus.RanToCompletion).ToArray();
}
Run Code Online (Sandbox Code Playgroud)

用法

var tasks = new[]
{
    Task.FromResult(1),
    Task.FromException<int>(new ArgumentException("fail1")),
    Task.FromException<int>(new ArgumentException("fail2"))
};

var succeed = await RejectFailedFrom(tasks);
// [ tasks[0] ]
Run Code Online (Sandbox Code Playgroud)


The*_*ias 5

当任务失败时,我们无法访问它的Result属性,因为它会抛出异常。因此,要获得部分成功WhenAll任务的结果,我们必须确保该任务能够成功完成。那么问题就变成了如何处理失败的内部任务的异常。吞下它们可能不是一个好主意。至少我们想记录它们。这是一个WhenAll从不抛出但返回ValueTuple结构中的结果和异常的替代方案的实现。

public static Task<(T[] Results, Exception[] Exceptions)> WhenAllEx<T>(
    params Task<T>[] tasks)
{
    tasks = tasks.ToArray(); // Defensive copy
    return Task.WhenAll(tasks).ContinueWith(t => // 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(); // Flatten the hierarchy of AggregateExceptions
        if (exceptions.Length == 0 && t.IsCanceled)
        {
            // No exceptions and at least one task was canceled
            exceptions = new[] { new TaskCanceledException(t) };
        }
        return (results, exceptions);
    }, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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))}");
Run Code Online (Sandbox Code Playgroud)

输出:

状态:RanToCompletion
结果:1
异常:fail1、fail2

  • @Alex,`async-await` 导致更清晰和可读的代码,并且实现比继续更轻,你应该记住使用 `ExecuteSynchronously` 选项。 (2认同)