web-dev-qa-db-fra.com

Manière correcte de gérer une exception dans une tâche continue avec

S'il vous plaît jeter un oeil sur le code suivant-

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);
    Console.ReadKey();
    Console.WriteLine("Hello");
}

private static int div(int x, int y)
{
    if (y == 0)
    {
        throw new ArgumentException("y");
    }
    return x / y;
}

Si j'exécute le code en mode validation, le résultat est "Une ou plusieurs erreurs sont survenues" et une fois que j'ai appuyé sur la touche "Entrée", "Bonjour" s'affiche également. Si j'exécute le code en mode débogage, le résultat est identique à mode de publication. Mais lors du débogage dans l'EDI, un IDE ("exception non gérée dans le code utilisateur")) apparaît lorsque le contrôle exécute la ligne

throw new ArgumentException("y"); 

Si je continue à partir de là, le programme ne plante pas et affiche le même résultat que le mode de publication. Est-ce la bonne façon de gérer les exceptions?

51
Anirban Paul

Vous n'avez probablement pas besoin de gestionnaires séparés OnlyOnFaulted et OnlyOnRanToCompletion, et vous ne gérez pas OnlyOnCanceled. Vérifiez cette réponse pour plus de détails.

Mais lors du débogage dans l'EDI, un message d'erreur IDE ("exception non gérée dans le code utilisateur")) lorsque le contrôle exécute la ligne

Vous voyez l’exception sous le débogueur parce que vous l’avez probablement activée dans les options Debug/Exceptions (Ctrl+Alt+E).

Si je continue à partir de là, le programme ne plante pas et affiche le même résultat que le mode de publication. Est-ce la bonne façon de gérer les exceptions?

Une exception levée mais non gérée dans une action Task ne sera pas automatiquement ré-levée. Au lieu de cela, il sera emballé pour l'observation future en tant que Task.Exception (de type AggregateException). Vous pouvez accéder à l'exception d'origine en tant que Exception.InnerException:

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;

Pour que le programme se bloque dans ce cas, vous devez en réalité observer l'exception extérieur l'action, par exemple. en référençant le Task.Result:

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);

    Console.ReadKey();

    Console.WriteLine("result: " + task.Result); // will crash here

    // you can also check task.Exception

    Console.WriteLine("Hello");
}

Plus de détails: Tâches et exceptions non gérées , Gestion des exceptions de tâches dans .NET 4.5.

Mis à jour pour répondre au commentaire: , voici comment je procéderais dans une application d'interface utilisateur avec .NET 4.0 et VS2010:

void Button_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => 
    {
        return div(32, 0); 
    }).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            // faulted with exception
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            MessageBox.Show("Error: " + ex.Message);
        }
        else if (t.IsCanceled)
        {
            // this should not happen 
            // as you don't pass a CancellationToken into your task
            MessageBox.Show("Canclled.");
        }
        else
        {
            // completed successfully
            MessageBox.Show("Result: " + t.Result);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

Tant que vous ciblez .NET 4.0 et que vous souhaitez que le comportement .NET 4.0 soit appliqué aux exceptions non observées (par exemple, une nouvelle tentative lorsque la tâche est lue), vous devez explicitement configurez-le dans le app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>

Vérifiez ceci pour plus de détails:

Exceptions de tâches non observées dans .NET4

63
noseratio

Ce que vous avez là est un AggregateException. Cela provient des tâches et vous oblige à vérifier les exceptions internes pour en trouver des spécifiques. Comme ça:

task.ContinueWith(t =>
{
    if (t.Exception is AggregateException) // is it an AggregateException?
    {
        var ae = t.Exception as AggregateException;

        foreach (var e in ae.InnerExceptions) // loop them and print their messages
        {
            Console.WriteLine(e.Message); // output is "y" .. because that's what you threw
        }
    }
},
TaskContinuationOptions.OnlyOnFaulted);
4
Simon Whitehead

A partir de .Net 4.5, vous pouvez utiliser AggregateException.GetBaseException() pour renvoyer "la cause première de cette exception".

https://docs.Microsoft.com/en-us/dotnet/api/system.aggregateexception.getbaseexception?view=netframework-4.7.2

La documentation semble être un peu en retrait cependant. Il prétend renvoyer une autre exception AggregateException. Cependant, je pense que vous constaterez qu'il renvoie l'erreur ArgumentException qui a été levée.

3
Cody Barnes

"Une ou plusieurs erreurs survenues" provient d'une exception d'encapsuleur créée par le pool de tâches. Utilisez Console.WriteLine(t.Exception.ToString()) pour imprimer toute l’exception si vous en avez besoin.

Les IDE peuvent capturer automatiquement toutes les exceptions, qu’elles aient été gérées ou non.

0
hsun324

Puisque vous utilisez des tâches, vous devriez obtenir AggregateException , qui englobe toutes les exceptions survenues au cours de l'exécution. Vous voyez le message One or more errors occurred, Parce que c'est la sortie par défaut de la méthode AggregateException.ToString().

Vous avez besoin de la méthode Handle de l'instance de l'exception.

Voir également la bonne approche pour gérer de telles exceptions here .

0
Tony