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

时间:2019-04-28 04:28:53

标签: 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();

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

这可能吗?

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

4 个答案:

答案 0 :(得分: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();
}

用法

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] ]

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

与@Theodor Zoulias强大而优雅的solution一起玩耍,使我有所收获。它看起来很黑,但仍然可以使用。可以继续Task.WhenAll使用不会肯定会引发异常的内容(例如_ => { })和Wait这样的内容。

var cts = new CancellationTokenSource();
cts.Cancel();
var canceled = Task.Run(() => 1, cts.Token);

var faulted = Task.FromException<int>(new Exception("Some Exception"));

var ranToCompletion = Task.FromResult(1);

var allTasks = new[] { canceled, faulted, ranToCompletion };

// wait all tasks to complete regardless anything
Task.WhenAll(allTasks).ContinueWith(_ => { }).Wait();

foreach(var t in allTasks)
{
    Console.WriteLine($"Task #{t.Id} {t.Status}");
    if (t.Status == TaskStatus.Faulted)
        foreach (var e in t.Exception.InnerExceptions)
            Console.WriteLine($"\t{e.Message}");
    if (t.Status == TaskStatus.RanToCompletion)
        Console.WriteLine($"\tResult: {t.Result}");
}

输出看起来像这样:

Task #2 Canceled
Task #1 Faulted
        Some Exception
Task #5 RanToCompletion
        Result: 1

答案 3 :(得分:1)

更改

onSnapshot()

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

Working example