3
votes

Est-il possible d'obtenir des résultats positifs à partir d'une tâche.QuandTout quand l'une des tâches échoue?

Compte tenu de ce qui suit:

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

l'appel à task.Wait () lève une AggregateException , dont les exceptions internes contiennent le fail1 et les exceptions fail2 . Mais comment puis-je accéder au résultat réussi de tPass1 ?

Est-ce possible?

Je sais que je peux obtenir le résultat de la tâche individuelle après le WhenAll est terminé, via tPass1.Result cependant y a-t-il un moyen de les mettre dans un tableau pour éviter d'avoir à suivre manuellement tout ce qui alimente le WhenAll ?


4 commentaires

Vous passez un tableau de tâches à Task.WhenAll , pouvez-vous utiliser le même tableau pour observer les résultats?


docs.microsoft. com / en-us / dotnet / api /…


Vous aurez bien sûr besoin d'un List ou d'un tableau pour suivre les résultats. Il n'y a pas de fée pour le faire à votre place. Pas clair si c'est ce que vous demandez.


@HenkHolterman fondamentalement oui je demande une fée pour suivre les résultats pour moi; Task.WhenAll suit déjà les résultats pour le cas où il n'y a pas d'exceptions, et il suit déjà les exceptions. J'espérais qu'il y avait simplement un moyen d'exposer ces informations existantes


4 Réponses :


7
votes

Peut-être

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

Utilisation

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


0 commentaires

5
votes

Lorsqu'une tâche échoue, nous ne pouvons pas accéder à son Result , car il lance. Donc, pour avoir les résultats d'une tâche WhenAll partiellement réussie, nous devons nous assurer que la tâche se terminera avec succès. Le problème devient alors ce qu'il faut faire avec les exceptions des tâches internes qui ont échoué. Les avaler n'est probablement pas une bonne idée. Au moins, nous aimerions les enregistrer. Voici une implémentation d'un WhenAll alternatif qui ne lève jamais, mais renvoie à la fois les résultats et les exceptions dans un ValueTuple struct.

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

Exemple d'utilisation:

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


8 commentaires

Bonne réponse. ContinueWith est toujours d'une grande aide avec les tâches .


@Alex, async-await conduit à un code beaucoup plus propre et lisible, et a une implémentation beaucoup plus légère que la continuation, où vous devez vous rappeler d'utiliser les options ExecuteSynchronously .


@Basin c'est vrai. Avec await dans le code de la bibliothèque, vous devez vous rappeler d'utiliser ConfigureAwait (false) cependant. 😃


Le cas @TheodorZoulias ConfigureAwait (false) dépend du scénario, ce n'est pas un must. Supposons que la capture du contexte soit réellement requise dans la bibliothèque elle-même.


@SarveshMishra c'est encore pire alors. Vous devez ConfigureAwait (vrai / faux) au cas par cas. Facile de faire une erreur. Plus on attend, plus il y a de possibilités d'erreur.


@TheodorZoulias, dans .NET Core, vous n'avez pas besoin d'utiliser ConfigureAwait .


@Fabio ici , il est indiqué que pour le code de la bibliothèque .NET Core, il est recommandé d'utiliser ConfigureAwait (false) .


@TheodorZoulias, il est également indiqué lorsque vous n'en avez pas besoin, ce qui est la plupart du temps pour les bibliothèques .NET Core.



1
votes

Jouer avec la solution puissante et élégante de @Theodor Zoulias m'a poussé à quelque chose. Cela a l'air hacky, mais fonctionne toujours. On peut continuer Task.WhenAll avec quelque chose qui ne lancera pas d'exception à coup sûr (par exemple _ => {} ) et Wait ce quelque chose.

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

La sortie ressemble à ceci:

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


4 commentaires

Puissant , mais loin de élégant ;)


@Basin, cela dépend de. Quant à moi, moins de code est meilleur code. Celui-ci est le plus court possible.


Désolé, mais l'approche ContinueWith produit beaucoup plus de code que l'approche avec wait


Il est produit dans les coulisses.



-1
votes

Change

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

vers

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

Exemple de travail


4 commentaires

À l'air cool. Mais êtes-vous sûr que cela fonctionnera avec des tâches très longues? Dans l'exemple, les défauts peuvent se produire immédiatement, comme de manière synchrone.


Je n'ai pas encore réfléchi. Va vérifier plus tard.


Je vois que ça t'a pris du temps,)


@Alex Il n'est jamais trop tard.