Share via


Gestion des exceptions (bibliothèque parallèle de tâches)

Les exceptions non gérées levées par le code utilisateur s'exécutant à l'intérieur d'une tâche sont propagées vers le thread joint, sauf dans certains scénarios décrits ultérieurement dans cette rubrique. Les exceptions sont propagées lorsque vous utilisez l'une des méthodes statiques ou d'instance Task.Wait ou Task<TResult>.Wait et lorsque vous les gérer en incluant l'appel dans une instruction try-catch. Si une tâche est le parent de tâches enfants attachées ou si vous attendez plusieurs tâches, plusieurs exceptions peuvent être levées. Pour propager toutes les exceptions vers le thread appelant, l'infrastructure de la tâche les encapsule dans une instance AggregateException. AggregateException a une propriété InnerExceptions qui peut être énumérée pour examiner toutes les exceptions d'origine levées, et les gérer individuellement (ou non). Même si une seule exception est levée, elle est encapsulée dans une AggregateException.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Throw New MyCustomException("I'm bad, but not too bad!")
                                  End Sub)

Try
    task1.Wait()
Catch ae As AggregateException
    ' Assume we know what's going on with this particular exception.
    ' Rethrow anything else. AggregateException.Handle provides
    ' another way to express this. See later example.
    For Each ex In ae.InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            Console.WriteLine(ex.Message)
        Else
            Throw
        End If
    Next

End Try
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("I'm bad, but not too bad!");
});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{
    // Assume we know what's going on with this particular exception.
    // Rethrow anything else. AggregateException.Handle provides
    // another way to express this. See later example.
    foreach (var e in ae.InnerExceptions)
    {
        if (e is MyCustomException)
        {
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }

}

Vous pouvez éviter une exception non gérée en interceptant la AggregateException et en n'observant aucune des exceptions internes. Toutefois, cela n'est pas recommandé car cela revient à intercepter le type Exception de base dans des scénarios non parallèles. Intercepter une exception sans prendre de mesures spécifiques pour la récupération peut laisser votre programme dans un état indéterminé.

Si vous n'attendez pas une tâche qui propage une exception ou accédez à sa propriété Exception, l'exception est transmise d'après la stratégie de l'exception .NET lorsque la tâche est récupérée par le garbage collector.

Lorsque les exceptions sont autorisées à se propager vers le thread joint, il est possible qu'une tâche continuer à traiter des éléments après que l'exception a été levée.

RemarqueRemarque

Lorsque l'option "Uniquement mon code" est activée, Visual Studio s'arrête dans certains cas sur la ligne qui lève l'exception et affiche un message d'erreur indiquant que l'exception n'est pas gérée par le code utilisateur. Cette erreur est bénigne.Vous pouvez appuyer sur F5 pour continuer et voir le comportement de gestion des exceptions illustré dans ces exemples.Pour empêcher Visual Studio de s'arrêter sur la première erreur, il vous suffit de désactiver la case à cocher "Uniquement mon code" sous Outils, Options, Débogage, Général.

Tâches enfants attachées et AggregateExceptions imbriqué

Si une tâche a une tâche enfant attachée qui lève une exception, cette exception est incluse dans un wrapper dans un AggregateException avant d'être propagée vers la tâche parent, qui inclut dans un wrapper cette exception dans son propre AggregateException avant de la propager vers le thread appelant. Dans de tels cas, la propriété AggregateException().InnerExceptions de la AggregateException interceptée à la méthode Task.Wait ou Task<TResult>.Wait ou WaitAny ou WaitAll contient une ou plusieurs instances AggregateException, et non les exceptions d'origine ayant provoqué l'erreur. Pour éviter de devoir itérer au sein du AggregateExceptions imbriqué, vous pouvez utiliser la méthode Flatten() pour supprimer tous les AggregateExceptions imbriqués, afin que la propriété AggregateException() InnerExceptions contienne les exceptions d'origine. Dans l'exemple suivant, les instances AggregateException imbriqués sont aplatie et gérées dans une seule boucle.

' task1 will throw an AE inside an AE inside an AE
Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim child1 = Task.Factory.StartNew(Sub()
                                                                             Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                    Throw New MyCustomException("Attached child2 faulted.")
                                                                                                                End Sub,
                                                                                                                TaskCreationOptions.AttachedToParent)
                                                                         End Sub,
                                                                         TaskCreationOptions.AttachedToParent)
                                      ' Uncomment this line to see the exception rethrown.
                                      ' throw new MyCustomException("Attached child1 faulted.")
                                  End Sub)
Try
    task1.Wait()
Catch ae As AggregateException
    For Each ex In ae.Flatten().InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            Console.WriteLine(ex.Message)
        Else
            Throw
        End If
    Next
    'or like this:
    '  ae.Flatten().Handle(Function(e)
    '                               Return TypeOf (e) Is MyCustomException
    '                   End Function)
End Try
// task1 will throw an AE inside an AE inside an AE
var task1 = Task.Factory.StartNew(() =>
{
    var child1 = Task.Factory.StartNew(() =>
        {
            var child2 = Task.Factory.StartNew(() =>
            {
                throw new MyCustomException("Attached child2 faulted.");
            },
            TaskCreationOptions.AttachedToParent);

            // Uncomment this line to see the exception rethrown.
            // throw new MyCustomException("Attached child1 faulted.");
        },
        TaskCreationOptions.AttachedToParent);
});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }
    // or ...
   // ae.Flatten().Handle((ex) => ex is MyCustomException);

}

Exceptions des tâches enfants détachées

Par défaut, les tâches enfants sont créées détachées. Les exceptions levées depuis des tâches détachées doivent être contrôlées ou levées de nouveau dans la tâche parent immédiate ; elles ne sont pas propagées vers le thread appelant de la même façon que les tâches enfants attachées. Le parent situé le plus en haut peut lever de nouveau une exception manuellement depuis un enfant détaché pour qu'elle soit encapsulée dans un AggregateException et propagée de nouveau vers le thread joint.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim nestedTask1 = Task.Factory.StartNew(Sub()
                                                                                  Throw New MyCustomException("Nested task faulted.")
                                                                              End Sub)
                                      ' Here the exception will be escalated back to joining thread.
                                      ' We could use try/catch here to prevent that.
                                      nestedTask1.Wait()
                                  End Sub)
Try
    task1.Wait()
Catch ae As AggregateException
    For Each ex In ae.Flatten().InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            ' Recover from the exception. Here we just
            ' print the message for demonstration purposes.
            Console.WriteLine(ex.Message)
        End If
    Next
End Try
var task1 = Task.Factory.StartNew(() =>
{

    var nested1 = Task.Factory.StartNew(() =>
    {
        throw new MyCustomException("Nested task faulted.");
    });

    // Here the exception will be escalated back to joining thread.
    // We could use try/catch here to prevent that.
    nested1.Wait();

});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
    }
}

Même si vous utilisez une continuation pour observer une exception dans une tâche enfant, l'exception doit toujours être observée par la tâche parent.

Exceptions indiquant l'annulation coopérative

Lorsque le code utilisateur d'une tâche répond à une requête d'annulation, la procédure correcte est de lever une OperationCanceledException qui passe le jeton d'annulation sur lequel la requête a été communiquée. Avant d'essayer de propager l'exception, l'instance de tâche compare le jeton de l'exception à celui qui lui a été passé lors de sa création. S'ils sont identiques, la tâche propage une TaskCanceledException encapsulée dans AggregateException, et cette dernière peut être affichée au moment d'examiner les exceptions internes. Toutefois, si le thread joint n'attend pas la tâche, cette exception spécifique ne sera pas propagée. Pour plus d'informations, consultez Annulation de tâches.

Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim ct As CancellationToken = token
                                      While someCondition = True
                                          ' Do some work...
                                          Thread.SpinWait(500000)
                                          ct.ThrowIfCancellationRequested()
                                      End While
                                  End Sub,
                                  token)
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50000);
        ct.ThrowIfCancellationRequested();
    }
},
token);

// No waiting required.

Utilisation de la méthode Handle pour filtrer les exceptions internes

Vous pouvez utiliser la méthode Handle() pour éliminer des exceptions que vous pouvez traiter comme « gérées » sans utiliser d'autre logique. Dans le délégué utilisateur fourni à Handle(), vous pouvez examiner le type d'exception ou sa propriété Message(), ou toute autre information le concernant qui vous permettra de déterminer si l'exception est sans gravité. Toutes les exceptions pour lesquelles le délégué retourne la valeur false sont immédiatement levées dans une nouvelle instance AggregateException après le retour de Handle().

L'extrait de code suivant utilise une boucle foreach sur les exceptions internes.

For Each ex In ae.InnerExceptions
    If TypeOf (ex) Is MyCustomException Then
        Console.WriteLine(ex.Message)
    Else
        Throw
    End If
Next
foreach (var e in ae.InnerExceptions)
{
    if (e is MyCustomException)
    {
        Console.WriteLine(e.Message);
    }
    else
    {
        throw;
    }
}

L'extrait de code suivant affiche l'utilisation fonctionnellement équivalente de la méthode Handle().

ae.Handle(Function(ex)
              Return TypeOf (ex) Is MyCustomException
          End Function)
ae.Handle((ex) =>
{
    return ex is MyCustomException;
});

Observation d'exceptions à l'aide de la propriété Task.Exception

Si une tâche se termine avec l'état Faulted, sa propriété Exception peut être examinée pour découvrir quelle exception a provoqué l'erreur. Un bon moyen d'observer la propriété Exception est d'utiliser une continuation qui s'exécute uniquement si l'antécédent rencontre une erreur, comme indiqué dans l'exemple suivant.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Throw New MyCustomException("task1 faulted.")
                                  End Sub).ContinueWith(Sub(t)
                                                            Console.WriteLine("I have observed a {0}", _
                                                                              t.Exception.InnerException.GetType().Name)
                                                        End Sub,
                                                        TaskContinuationOptions.OnlyOnFaulted)
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
    {
        Console.WriteLine("I have observed a {0}",
            t.Exception.InnerException.GetType().Name);
    },
    TaskContinuationOptions.OnlyOnFaulted);

Dans une application réelle, le délégué de continuation peut enregistrer des informations détaillées sur l'exception, voire générer de nouvelles tâches pour la récupération.

Événement UnobservedTaskException

Dans certains scénarios, tels que lors de l'hébergement de plug-in non fiables, les exceptions bénignes peuvent être courantes et il peut s'avérer trop difficile de toutes les observer manuellement. Dans ces cas, vous pouvez gérer l'événement TaskScheduler.UnobservedTaskException. L'instance System.Threading.Tasks.UnobservedTaskExceptionEventArgs passée à votre gestionnaire peut être utilisée pour empêcher l'exception non prise en charge d'être repropagée vers le thread joint.

Voir aussi

Concepts

Bibliothèque parallèle de tâches