Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Необработанные исключения, создаваемые пользовательским кодом, выполняющимся внутри задачи, распространяются обратно в вызывающий поток, за исключением определенных сценариев, описанных далее в этом разделе. Исключения распространяются при использовании одного из статических или экземплярных Task.Wait методов и обрабатывают их путем заключения вызова в инструкцию/try
catch
. Если задача является родительской по отношению к дочерним задачам или если вы ожидаете выполнения нескольких задач, может быть брошено несколько исключений.
Чтобы распространить все исключения обратно в вызывающий поток, инфраструктура задач упаковывает их в AggregateException экземпляр. Исключение AggregateException имеет InnerExceptions свойство, которое можно перечислить для проверки всех исходных исключений, которые были созданы, и обрабатывать (или не обрабатывать) каждый из них по отдельности. Вы также можете обрабатывать исходные исключения с помощью AggregateException.Handle метода.
Даже если выбрасывается только одно исключение, оно по-прежнему оборачивается в исключение AggregateException, как показано в следующем примере.
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!
Вы можете избежать необработанного исключения, просто перехватив AggregateException и не наблюдая ни одного из внутренних исключений. Однако мы рекомендуем не делать этого, так как это аналогично перехвату базового типа Exception в непараллельных сценариях. Чтобы перехватывать исключение, не выполняя определенные действия для восстановления из нее, можно оставить программу в неопределенном состоянии.
Если вы не хотите вызывать метод Task.Wait для того чтобы дождаться завершения задачи, можно также извлечь исключение AggregateException из свойства задачи Exception, как показано в следующем примере. Дополнительные сведения см. в разделе "Наблюдение исключений с помощью свойства Task.Exception" в этом разделе.
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!
Осторожность
В приведенном выше примере кода содержится while
цикл, который опрашивает свойство задачи Task.IsCompleted , чтобы определить, когда задача завершена. Это никогда не должно быть сделано в рабочем коде, так как это очень неэффективно.
Если вы не дожидаетесь завершения задачи, которая распространяет исключение, или не обращаетесь к ее свойству Exception, это исключение будет передано в соответствии с политикой обработки исключений .NET, когда задача будет удалена сборщиком мусора.
Если исключения разрешены подниматься обратно в поток, который их ожидает, возможно, что задача может продолжать обрабатывать некоторые элементы после возникновения исключения.
Примечание.
Если включен параметр "Just My Code", Visual Studio в некоторых случаях остановится на строке, которая вызывает исключение, и выведет сообщение об ошибке "исключение не обработано пользовательским кодом". Эта ошибка безвредна. Вы можете нажать клавишу F5, чтобы продолжить и увидеть поведение обработки исключений, которое демонстрируется в этих примерах. Чтобы Visual Studio не останавливалась при первой ошибке, просто снимите флажок "Включить только мой код" в разделе "Инструменты", "Параметры", "Отладка", "Общие".
Привязанные дочерние задачи и вложенные AggregateExceptions
Если у задачи есть присоединенная дочерняя задача, которая выбрасывает исключение, это исключение сначала оборачивается в AggregateException перед распространением в родительскую задачу, которая оборачивает его в свою собственную AggregateException, прежде чем распространить обратно в вызывающий поток. В таких случаях свойство InnerExceptions исключения AggregateException, пойманное на методе Task.Wait, WaitAny или WaitAll, содержит один или несколько экземпляров AggregateException, а не исходные исключения, которые вызвали ошибку. Чтобы избежать необходимости итерировать вложенные AggregateException исключения, можно использовать метод Flatten для удаления всех вложенных AggregateException исключений, чтобы свойство AggregateException.InnerExceptions содержало исходные исключения. В следующем примере вложенные AggregateException экземпляры сглаживаются и обрабатываются только в одном цикле.
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.
Вы также можете использовать метод AggregateException.Flatten для повторного выброса внутренних исключений из нескольких AggregateException экземпляров, возникающих из-за нескольких задач, в одном AggregateException экземпляре, как показано в следующем примере.
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.
Исключения из отсоединяемых дочерних задач
По умолчанию дочерние задачи создаются в откреплённом состоянии. Исключения, создаваемые из отсоединяемых задач, должны обрабатываться или перекинуты обратно в непосредственную родительскую задачу; они не распространяются обратно в вызывающий поток так же, как это делают присоединенные дочерние задачи. Самый верхний родительский объект может вручную повторно выбросить исключение из отсоединенного дочернего элемента, чтобы обернуть его в AggregateException и распространить обратно в вызывающий поток.
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.
Даже если вы используете продолжение выполнения для наблюдения за исключением в дочерней задаче, родительская задача всё равно должна наблюдать за исключением.
Исключения, указывающие на координированную отмену
Когда пользовательский код в задаче обрабатывает запрос на отмену, правильная процедура заключается в выбрасывании исключения OperationCanceledException, передав токен отмены, по которому был передан запрос. Прежде чем пытаться распространить исключение, экземпляр задачи сравнивает токен в исключении с токеном, который был передан ему при создании. Если они одинаковы, задача передаёт TaskCanceledException, обернутую в AggregateException, и это можно увидеть при анализе внутренних исключений. Однако если вызывающий поток не ожидает задачи, данное исключение не будет распространяться. Для получения дополнительной информации см. Отмена задач.
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)
Использование метода handle для фильтрации внутренних исключений
Этот метод можно использовать AggregateException.Handle для фильтрации исключений, которые можно рассматривать как "обработанные" без использования дополнительной логики. В делегате пользователя, предоставленном методу AggregateException.Handle(Func<Exception,Boolean>), можно проверить тип исключения, его свойство Message или любую другую информацию о нём, которая позволит определить, является ли оно безвредным. Исключения, для которых делегат возвращает false
, повторно возбуждаются в новом экземпляре AggregateException сразу после того, как метод AggregateException.Handle возвращает управление.
Следующий пример функционально эквивалентен первому примеру в этом разделе, который проверяет каждое AggregateException.InnerExceptions исключение в коллекции. Вместо этого этот обработчик исключений вызывает объект метода AggregateException.Handle для каждого исключения и повторно генерирует только те исключения, которые не являются экземплярами 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!
Ниже приведен более полный пример, в котором метод AggregateException.Handle используется для специальной обработки исключения UnauthorizedAccessException при перечислении файлов.
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.
Наблюдение за исключениями с помощью свойства Task.Exception
Если задача завершается в TaskStatus.Faulted состоянии, его Exception свойство можно проверить, чтобы определить, какое конкретное исключение вызвало ошибку. Хорошим способом наблюдения свойства Exception является использование продолжения, которое выполняется только в том случае, если предыдущая задача завершится ошибкой, как показано в следующем примере.
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.
В содержательном приложении делегат продолжения может записывать подробные сведения об исключении и, возможно, создавать новые задачи для восстановления после исключения. Если задача неисправна, в следующих выражениях возникает исключение:
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Используйте инструкцию try-catch
для управления и отслеживания выбрасываемых исключений. В качестве альтернативы, наблюдайте за исключением, получая доступ к свойству Task.Exception.
Это важно
Невозможно явно поймать AggregateException, используя следующие выражения:
await task
task.GetAwaiter().GetResult()
Событие UnobservedTaskException
В некоторых сценариях, например, при размещении ненадежных подключаемых модулей, незначительные исключения могут быть частыми, и вручную наблюдать за всеми из них может быть слишком сложно. В таких случаях можно обрабатывать событие TaskScheduler.UnobservedTaskException. Экземпляр System.Threading.Tasks.UnobservedTaskExceptionEventArgs , передаваемый обработчику, можно использовать для предотвращения распространения необработанного исключения обратно в поток присоединения.