Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las excepciones no controladas que se lanzan por el código de usuario que se ejecuta dentro de una tarea se propagan de nuevo al hilo de llamada, excepto en determinados escenarios que se describen más adelante en este tema. Las excepciones se propagan cuando se usa uno de los métodos estáticos o de instancia Task.Wait , y se controlan mediante la inclusión de la llamada en una try
/catch
instrucción . Si una tarea es la tarea primaria de tareas secundarias asociadas o si se esperan varias tareas, pueden producirse varias excepciones.
Para propagar todas las excepciones de nuevo al subproceso que realiza la llamada, la infraestructura de la tarea las encapsula en una instancia de AggregateException . La AggregateException excepción tiene una InnerExceptions propiedad que se puede enumerar para examinar todas las excepciones originales que fueron lanzadas, y manejar (o no manejar) cada una individualmente. También puede controlar las excepciones originales mediante el AggregateException.Handle método .
Incluso si solo se produce una excepción, todavía se encapsula en una AggregateException excepción, como se muestra en el ejemplo siguiente.
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!
Puede evitar una excepción no controlada simplemente detectando AggregateException y no observando ninguna de las excepciones internas. Sin embargo, se recomienda no hacerlo porque es análogo a detectar el tipo base Exception en escenarios no paralelos. Para detectar una excepción sin realizar acciones específicas para recuperarse de ella, puede dejar el programa en un estado indeterminado.
Si no desea llamar al Task.Wait método para esperar la finalización de una tarea, también puede recuperar la AggregateException excepción de la propiedad de Exception la tarea, como se muestra en el ejemplo siguiente. Para obtener más información, vea la sección Observación de excepciones mediante la propiedad Task.Exception de este tema.
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!
Precaución
El código de ejemplo anterior incluye un while
bucle que sondea la propiedad de Task.IsCompleted la tarea para determinar cuándo se ha completado la tarea. Esto nunca debe hacerse en el código de producción, ya que es muy ineficaz.
Si no espera a una tarea que propague la excepción ni accede a su propiedad Exception , la excepción se escalará conforme a la directiva de excepciones de .NET cuando la tarea se recopile como elemento no utilizado.
Cuando las excepciones pueden propagarse de vuelta al subproceso de unión, es posible que una tarea continúe procesando algunos elementos después de que se haya producido la excepción.
Nota:
Cuando "Solo mi código" está habilitado, Visual Studio en algunos casos se interrumpirá en la línea que produce la excepción y mostrará un mensaje de error que indica "excepción no controlada por código de usuario". Este error es benigno. Puede presionar F5 para continuar y ver el comportamiento de control de excepciones que se muestra en estos ejemplos. Para evitar que Visual Studio se interrumpa con el primer error, desactive la casilla Habilitar Solo mi código bajo Herramientas, Opciones, Depuración, General.
Tareas secundarias asociadas y objetos AggregateException anidados
Si una tarea tiene una tarea secundaria adjunta que inicia una excepción, esa excepción se encapsula en un objeto AggregateException antes de que se propague a la tarea primaria, que encapsula esa excepción en su propio objeto AggregateException antes de propagarla de nuevo al subproceso que realiza la llamada. En tales casos, la propiedad InnerExceptions de la excepción AggregateException capturada en el método Task.Wait, WaitAny o WaitAll contiene una o varias instancias de AggregateException, no las excepciones originales que causaron la falla. Para evitar tener que iterar sobre excepciones anidadas AggregateException, puede usar el método Flatten para quitar todas las AggregateException excepciones anidadas, de modo que la propiedad AggregateException.InnerExceptions contenga las excepciones originales. En el ejemplo siguiente, las instancias anidadas de AggregateException se reducen y se controlan en un solo bucle.
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.
También puede usar el método AggregateException.Flatten para relanzar las excepciones internas de múltiples instancias AggregateException producidas por varias tareas en una sola instancia AggregateException, como se muestra en el ejemplo siguiente.
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.
Excepciones de tareas secundarias desasociadas
De forma predeterminada, las tareas secundarias están desasociadas cuando se crean. Las excepciones producidas por tareas desasociadas deben controlarse o reiniciarse en la tarea primaria inmediata; no se propagan de nuevo al subproceso que realiza la llamada del mismo modo que las tareas secundarias asociadas. La tarea primaria superior puede reiniciar manualmente una excepción de una tarea secundaria desasociada para que se encapsule en un objeto AggregateException y propagarla de vuelta al subproceso de unión.
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.
Aunque se use una tarea de continuación para observar una excepción en una tarea secundaria, la tarea primaria debe seguir observando la excepción.
Excepciones que indican la cancelación cooperativa
Cuando el código de usuario de una tarea responde a una solicitud de cancelación, el procedimiento correcto es lanzar un OperationCanceledException pasando el token de cancelación en el que se ha comunicado la solicitud. Antes de intentar propagar la excepción, la instancia de la tarea compara el token de la excepción con el que recibió durante su creación. Si son iguales, la tarea propaga un TaskCanceledException envuelto en AggregateException, y se puede ver cuando se examinan las excepciones internas. Sin embargo, si el subproceso que hace la llamada no está esperando la tarea, no se propagará esa excepción concreta. Para obtener más información, consulte Cancelación de tareas.
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)
Uso del método handle para filtrar excepciones internas
Puede usar el AggregateException.Handle método para filtrar las excepciones que puede tratar como "controladas" sin usar ninguna lógica adicional. En el delegado de usuario que se facilita al método AggregateException.Handle(Func<Exception,Boolean>) , se puede examinar el tipo de excepción, su propiedad Message o cualquier otra información sobre ella que permita determinar si no supone un riesgo. Las excepciones en las que el delegado devuelve false
se reinician inmediatamente en una nueva instancia de AggregateException después de que el método AggregateException.Handle devuelva un valor.
El ejemplo siguiente es funcionalmente equivalente al primer ejemplo de este tema, que examina cada excepción de la AggregateException.InnerExceptions colección. En su lugar, este controlador de excepciones llama al objeto del método AggregateException.Handle por cada excepción y solo vuelve a generar las excepciones que no son instancias de 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!
A continuación se muestra un ejemplo más completo que usa el AggregateException.Handle método para proporcionar un control especial para una UnauthorizedAccessException excepción al enumerar archivos.
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.
Observación de excepciones mediante la propiedad Task.Exception
Si una tarea se completa en el TaskStatus.Faulted estado, se puede examinar su Exception propiedad para detectar qué excepción específica provocó el error. Una buena manera de observar la Exception propiedad es usar una continuación que solo se ejecuta si se produce un error de tarea antecedente, como se muestra en el ejemplo siguiente.
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.
En una aplicación significativa, el delegado de continuación podría registrar información detallada sobre la excepción y posiblemente generar nuevas tareas para recuperarse de la excepción. Si se produce un error en una tarea, las expresiones siguientes lanzan la excepción:
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Use una instrucción try-catch
para controlar y observar las excepciones producidas. Como alternativa, observe la excepción accediendo a la Task.Exception propiedad .
Importante
No se puede capturar explícitamente el AggregateException cuando se usan las siguientes expresiones:
await task
task.GetAwaiter().GetResult()
Evento UnobservedTaskException
En algunos escenarios, como cuando se hospedan complementos que no son de confianza, las excepciones benignas pueden ser comunes y puede ser demasiado difícil observarlas manualmente todas. En estos casos, se puede proceder a controlar el evento TaskScheduler.UnobservedTaskException . La instancia de System.Threading.Tasks.UnobservedTaskExceptionEventArgs que se pasa al controlador se puede utilizar para evitar que la excepción no observada se propague de nuevo al subproceso de unión.