Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 appelant, sauf dans certains scénarios décrits plus loin dans cette rubrique. Les exceptions sont propagées lorsque vous utilisez l’une des méthodes statiques ou d’instance Task.Wait , et que vous les gérez en plaçant l’appel dans une try
/catch
instruction. 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 au thread appelant, l’infrastructure de tâche les encapsule dans une AggregateException instance. L’exception AggregateException a une InnerExceptions propriété qui peut être énumérée pour examiner toutes les exceptions d’origine levées et gérer (ou non) chacune d’elles individuellement. Vous pouvez également gérer les exceptions d’origine à l’aide de la AggregateException.Handle méthode.
Même si une seule exception est levée, elle est toujours encapsulée dans une AggregateException exception, comme l’illustre l’exemple suivant.
public static partial class Program
{
public static void HandleThree()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
// Rethrow any other exception.
else
{
throw ex;
}
}
}
}
}
// The example displays the following output:
// This exception is expected!
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw ex
End If
Next
End Try
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!
Vous pouvez éviter une exception non gérée en interceptant simplement l’exception AggregateException interne et en n’observant aucune des exceptions internes. Toutefois, nous vous recommandons de ne pas effectuer cette action, car cela est analogue à l’interception du type de base Exception dans des scénarios non parallèles. Intercepter une exception sans prendre de mesures spécifiques de récupération peut laisser votre programme dans un état indéterminé.
Si vous ne souhaitez pas appeler la Task.Wait méthode pour attendre la fin d’une tâche, vous pouvez également récupérer l’exception AggregateException de la propriété de Exception la tâche, comme l’illustre l’exemple suivant. Pour plus d’informations, consultez les exceptions d’observation à l’aide de la section de propriété Task.Exception dans cette rubrique.
public static partial class Program
{
public static void HandleFour()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
while (!task.IsCompleted) { }
if (task.Status == TaskStatus.Faulted)
{
foreach (var ex in task.Exception?.InnerExceptions ?? new(Array.Empty<Exception>()))
{
// Handle the custom exception.
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
// Rethrow any other exception.
else
{
throw ex;
}
}
}
}
}
// The example displays the following output:
// This exception is expected!
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))
While Not task1.IsCompleted
End While
If task1.Status = TaskStatus.Faulted Then
For Each ex In task1.Exception.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw ex
End If
Next
End If
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!
Avertissement
L’exemple de code précédent inclut une while
boucle qui interroge la propriété de Task.IsCompleted la tâche pour déterminer quand la tâche est terminée. Cela ne doit jamais être fait dans le code de production, car il est très inefficace.
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 des exceptions sont autorisées à remonter dans le thread de jointure, il est possible qu’une tâche continue à traiter certains éléments une fois l’exception levée.
Remarque
Lorsque « Just My Code » est activé, Visual Studio s’interrompt dans certains cas sur la ligne qui lève l’exception et affiche un message d’erreur indiquant « exception non 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 rompre avec la première erreur, décochez simplement la case Activer uniquement mon code sous Outils, Options, Débogage, Général.
Tâches enfants attachées et exceptions AggregateException imbriquées
Si une tâche a une tâche enfant attachée qui lève une exception, cette exception est encapsulée dans une exception AggregateException avant d’être propagée vers la tâche parent, qui encapsule cette exception dans sa propre exception AggregateException avant de la propager vers le thread appelant. Dans de tels cas, la propriété InnerExceptions de l'exception AggregateException interceptée au niveau de Task.Wait, WaitAny et WaitAll contient une ou plusieurs instances AggregateException, et non les exceptions d’origine qui ont provoqué l’erreur. Pour éviter d’avoir à itérer sur les exceptions imbriquées AggregateException , vous pouvez utiliser la Flatten méthode pour supprimer toutes les exceptions imbriquées AggregateException afin que la AggregateException.InnerExceptions propriété contienne les exceptions d’origine. Dans l'exemple suivant, les instances imbriquées AggregateException sont aplaties et traitées dans une seule boucle.
public static partial class Program
{
public static void FlattenTwo()
{
var task = Task.Factory.StartNew(() =>
{
var child = Task.Factory.StartNew(() =>
{
var grandChild = Task.Factory.StartNew(() =>
{
// This exception is nested inside three AggregateExceptions.
throw new CustomException("Attached child2 faulted.");
}, TaskCreationOptions.AttachedToParent);
// This exception is nested inside two AggregateExceptions.
throw new CustomException("Attached child1 faulted.");
}, TaskCreationOptions.AttachedToParent);
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var ex in ae.Flatten().InnerExceptions)
{
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
else
{
throw;
}
}
}
}
}
// The example displays the following output:
// Attached child1 faulted.
// Attached child2 faulted.
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Dim child1 = Task.Factory.StartNew(Sub()
Dim child2 = Task.Factory.StartNew(Sub()
Throw New CustomException("Attached child2 faulted.")
End Sub,
TaskCreationOptions.AttachedToParent)
Throw New CustomException("Attached child1 faulted.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
End Try
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Attached child1 faulted.
' Attached child2 faulted.
Vous pouvez également utiliser la méthode AggregateException.Flatten pour relancer les exceptions internes de plusieurs instances AggregateException soulevées par plusieurs tâches dans une seule instance AggregateException, comme le montre l'exemple suivant.
public static partial class Program
{
public static void TaskExceptionTwo()
{
try
{
ExecuteTasks();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
Console.WriteLine($"{e.GetType().Name}:\n {e.Message}");
}
}
}
static void ExecuteTasks()
{
// Assume this is a user-entered String.
string path = @"C:\";
List<Task> tasks = new();
tasks.Add(Task.Run(() =>
{
// This should throw an UnauthorizedAccessException.
return Directory.GetFiles(
path, "*.txt",
SearchOption.AllDirectories);
}));
tasks.Add(Task.Run(() =>
{
if (path == @"C:\")
{
throw new ArgumentException(
"The system root is not a valid path.");
}
return new string[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
}));
tasks.Add(Task.Run(() =>
{
throw new NotImplementedException(
"This operation has not been implemented.");
}));
try
{
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}
}
// The example displays the following output:
// UnauthorizedAccessException:
// Access to the path 'C:\Documents and Settings' is denied.
// ArgumentException:
// The system root is not a valid path.
// NotImplementedException:
// This operation has not been implemented.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Try
ExecuteTasks()
Catch ae As AggregateException
For Each e In ae.InnerExceptions
Console.WriteLine("{0}:{2} {1}", e.GetType().Name, e.Message,
vbCrLf)
Next
End Try
End Sub
Sub ExecuteTasks()
' Assume this is a user-entered String.
Dim path = "C:\"
Dim tasks As New List(Of Task)
tasks.Add(Task.Run(Function()
' This should throw an UnauthorizedAccessException.
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function))
tasks.Add(Task.Run(Function()
If path = "C:\" Then
Throw New ArgumentException("The system root is not a valid path.")
End If
Return {".txt", ".dll", ".exe", ".bin", ".dat"}
End Function))
tasks.Add(Task.Run(Sub()
Throw New NotImplementedException("This operation has not been implemented.")
End Sub))
Try
Task.WaitAll(tasks.ToArray)
Catch ae As AggregateException
Throw ae.Flatten()
End Try
End Sub
End Module
' The example displays the following output:
' UnauthorizedAccessException:
' Access to the path 'C:\Documents and Settings' is denied.
' ArgumentException:
' The system root is not a valid path.
' NotImplementedException:
' This operation has not been implemented.
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 gérées ou levées à 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 le plus haut peut lever à nouveau manuellement une exception à partir d’un enfant détaché pour l’encapsuler dans une exception AggregateException et la propager vers le thread appelant.
public static partial class Program
{
public static void DetachedTwo()
{
var task = Task.Run(() =>
{
var nestedTask = Task.Run(
() => throw new CustomException("Detached child task faulted."));
// Here the exception will be escalated back to the calling thread.
// We could use try/catch here to prevent that.
nestedTask.Wait();
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.Flatten().InnerExceptions)
{
if (e is CustomException)
{
Console.WriteLine(e.Message);
}
}
}
}
}
// The example displays the following output:
// Detached child task faulted.
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub()
Dim nestedTask1 = Task.Run(Sub()
Throw New CustomException("Detached child 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 CustomException Then
' Recover from the exception. Here we just
' print the message for demonstration purposes.
Console.WriteLine(ex.Message)
End If
Next
End Try
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Detached child task faulted.
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 qui indiquent une annulation coopérative
Lorsque le code utilisateur d’une tâche répond à une demande d’annulation, la procédure correcte consiste à lever une exception OperationCanceledException qui passe le jeton d’annulation sur lequel la demande a été communiquée. Avant de tenter de propager l'exception, l'instance de tâche compare le jeton dans l'exception à celui qui lui a été transmis lors de sa création. S'ils sont identiques, la tâche propage un élément encapsulé TaskCanceledException dans le AggregateException, et cela peut être observé lorsque les exceptions internes sont examinées. Toutefois, si le thread appelant n’est pas en attente sur la tâche, cette exception spécifique n’est pas propagée. Pour plus d’informations, consultez Annulation de tâche.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
CancellationToken ct = token;
while (someCondition)
{
// Do some work...
Thread.SpinWait(50_000);
ct.ThrowIfCancellationRequested();
}
},
token);
// No waiting required.
tokenSource.Dispose();
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)
Utilisation de la méthode handle pour filtrer les exceptions internes
Vous pouvez utiliser la AggregateException.Handle méthode pour filtrer les exceptions que vous pouvez traiter comme « gérées » sans utiliser de logique supplémentaire. Dans le délégué de l'utilisateur fourni à la méthode AggregateException.Handle(Func<Exception,Boolean>), vous pouvez examiner le type d’exception, sa propriété Message ou toute autre information sur celle-ci qui vous permettra de déterminer si elle est bénigne. Toutes les exceptions pour lesquelles le délégué retourne la valeur false
sont levées à nouveau dans une nouvelle instance AggregateException dès le retour de la méthode AggregateException.Handle.
L’exemple suivant équivaut fonctionnellement au premier exemple de cette rubrique, qui examine chaque exception de la AggregateException.InnerExceptions collection. Au lieu de cela, ce gestionnaire d’exceptions appelle la méthode de l'objet AggregateException.Handle pour chaque exception, et relance uniquement celles qui ne sont pas des instances CustomException
.
public static partial class Program
{
public static void HandleMethodThree()
{
var task = Task.Run(
() => throw new CustomException("This exception is expected!"));
try
{
task.Wait();
}
catch (AggregateException ae)
{
// Call the Handle method to handle the custom exception,
// otherwise rethrow the exception.
ae.Handle(ex =>
{
if (ex is CustomException)
{
Console.WriteLine(ex.Message);
}
return ex is CustomException;
});
}
}
}
// The example displays the following output:
// This exception is expected!
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))
Try
task1.Wait()
Catch ae As AggregateException
' Call the Handle method to handle the custom exception,
' otherwise rethrow the exception.
ae.Handle(Function(e)
If TypeOf e Is CustomException Then
Console.WriteLine(e.Message)
End If
Return TypeOf e Is CustomException
End Function)
End Try
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!
Voici un exemple plus complet qui utilise la AggregateException.Handle méthode pour fournir une gestion spéciale pour une UnauthorizedAccessException exception lors de l’énumération de fichiers.
public static partial class Program
{
public static void TaskException()
{
// This should throw an UnauthorizedAccessException.
try
{
if (GetAllFiles(@"C:\") is { Length: > 0 } files)
{
foreach (var file in files)
{
Console.WriteLine(file);
}
}
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
}
Console.WriteLine();
// This should throw an ArgumentException.
try
{
foreach (var s in GetAllFiles(""))
{
Console.WriteLine(s);
}
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
}
static string[] GetAllFiles(string path)
{
var task1 =
Task.Run(() => Directory.GetFiles(
path, "*.txt",
SearchOption.AllDirectories));
try
{
return task1.Result;
}
catch (AggregateException ae)
{
ae.Handle(x =>
{
// Handle an UnauthorizedAccessException
if (x is UnauthorizedAccessException)
{
Console.WriteLine(
"You do not have permission to access all folders in this path.");
Console.WriteLine(
"See your network administrator or try another path.");
}
return x is UnauthorizedAccessException;
});
return Array.Empty<string>();
}
}
}
// The example displays the following output:
// You do not have permission to access all folders in this path.
// See your network administrator or try another path.
//
// ArgumentException: The path is not of a legal form.
Imports System.IO
Imports System.Threading.Tasks
Module Example
Public Sub Main()
' This should throw an UnauthorizedAccessException.
Try
Dim files = GetAllFiles("C:\")
If files IsNot Nothing Then
For Each file In files
Console.WriteLine(file)
Next
End If
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()
' This should throw an ArgumentException.
Try
For Each s In GetAllFiles("")
Console.WriteLine(s)
Next
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()
End Sub
Function GetAllFiles(ByVal path As String) As String()
Dim task1 = Task.Run(Function()
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function)
Try
Return task1.Result
Catch ae As AggregateException
ae.Handle(Function(x)
' Handle an UnauthorizedAccessException
If TypeOf x Is UnauthorizedAccessException Then
Console.WriteLine("You do not have permission to access all folders in this path.")
Console.WriteLine("See your network administrator or try another path.")
End If
Return TypeOf x Is UnauthorizedAccessException
End Function)
End Try
Return Array.Empty(Of String)()
End Function
End Module
' The example displays the following output:
' You do not have permission to access all folders in this path.
' See your network administrator or try another path.
'
' ArgumentException: The path is not of a legal form.
Observation d’exceptions à l’aide de la propriété Task.Exception
Si une tâche se termine dans l’état TaskStatus.Faulted , sa Exception propriété peut être examinée pour découvrir quelle exception spécifique a provoqué l’erreur. Une bonne façon d’observer la Exception propriété consiste à utiliser une continuation qui s’exécute uniquement si la tâche antécédente échoue, ainsi que illustré dans l’exemple suivant.
public static partial class Program
{
public static void ExceptionPropagationTwo()
{
_ = Task.Run(
() => throw new CustomException("task1 faulted."))
.ContinueWith(_ =>
{
if (_.Exception?.InnerException is { } inner)
{
Console.WriteLine($"{inner.GetType().Name}: {inner.Message}");
}
},
TaskContinuationOptions.OnlyOnFaulted);
Thread.Sleep(500);
}
}
// The example displays output like the following:
// CustomException: task1 faulted.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Throw New CustomException("task1 faulted.")
End Sub).
ContinueWith(Sub(t)
Console.WriteLine("{0}: {1}",
t.Exception.InnerException.GetType().Name,
t.Exception.InnerException.Message)
End Sub, TaskContinuationOptions.OnlyOnFaulted)
Thread.Sleep(500)
End Sub
End Module
Class CustomException : Inherits Exception
Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays output like the following:
' CustomException: task1 faulted.
Dans une application significative, le délégué de continuation peut consigner des informations détaillées sur l’exception et générer éventuellement de nouvelles tâches à récupérer à partir de l’exception. Si une tâche est défaillante, les expressions suivantes lèvent l’exception :
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Utilisez une instruction try-catch
pour gérer et observer les exceptions levées. Vous pouvez également observer l’exception en accédant à la Task.Exception propriété.
Importante
Il est impossible d’intercepter explicitement AggregateException lors de l’utilisation des expressions suivantes :
await task
task.GetAwaiter().GetResult()
Événement UnobservedTaskException
Dans certains scénarios, par exemple lors de l’hébergement de plug-ins non approuvés, des exceptions bénignes peuvent être courantes et il peut être trop difficile de les observer manuellement. Dans ces cas, vous pouvez gérer l’événement TaskScheduler.UnobservedTaskException . Il est possible d’utiliser l’instance System.Threading.Tasks.UnobservedTaskExceptionEventArgs passée à votre gestionnaire pour empêcher la propagation de l’exception non prise en charge vers le thread joint.