Можно ли получить успешные результаты от задачи. Когда все не удается выполнить одну из задач? - PullRequest
2 голосов
/ 28 апреля 2019

Учитывая следующее:

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, внутренние исключения которого содержат исключения fail1 и fail2.Но как я могу получить доступ к tPass1 успешному результату?

Возможно ли это?

Я знаю, что могу получить результат от индивидуального задания после завершения WhenAll,через tPass1.Result однако есть способ получить их в массив, чтобы избежать необходимости вручную отслеживать все вещи, поступающие в WhenAll?

Ответы [ 4 ]

5 голосов
/ 28 апреля 2019

Может

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] ]
4 голосов
/ 28 апреля 2019

Когда задача не выполняется, мы не можем получить доступ к ее свойству 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
Исключения: сбой1, сбой2

1 голос
/ 15 июня 2019

Изменение

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

до

var all = new Task<int>[] { tPass1, tFail1, tFail2 }
    .Where(t => t.Status == TaskStatus.RanToCompletion);
var task = Task.WhenAll(all);
task.Wait();

Рабочий пример

1 голос
/ 29 апреля 2019

Игра с мощным и элегантным решением @ 1002 * от @Theodor Zoulias подтолкнула меня к чему-то.Это выглядит неприлично, но все еще работает.Можно продолжить 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
...