Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Le eccezioni non gestite generate dal codice utente in esecuzione all'interno di un'attività vengono propagate nuovamente al thread chiamante, ad eccezione di alcuni scenari descritti più avanti in questo argomento. Le eccezioni vengono propagate quando si usa uno dei metodi statici o dell'istanza Task.Wait e le si gestiscono racchiudendo la chiamata in un'istruzionetry/catch . Se un'attività è l'elemento padre di attività figlio associate o se si è in attesa di più attività, potrebbero essere generate più eccezioni.
Per propagare tutte le eccezioni al thread chiamante, l'infrastruttura di attività le racchiude in un'istanza di AggregateException. L'eccezione AggregateException ha una proprietà InnerExceptions che può essere enumerata per esaminare tutte le eccezioni originali sollevate e gestire (o non gestire) ognuna singolarmente. È anche possibile gestire le eccezioni originali usando il AggregateException.Handle metodo .
Anche se viene generata solo un'eccezione, è comunque racchiusa in un'eccezione AggregateException, come illustrato nell'esempio seguente.
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!
È possibile evitare un'eccezione non gestita semplicemente rilevando AggregateException e non osservando alcuna delle eccezioni interne. Tuttavia, è consigliabile non eseguire questa operazione perché è analogo a intercettare il tipo di base Exception in scenari non paralleli. Per intercettare un'eccezione senza eseguire azioni specifiche per riprendersi da essa, si può lasciare il programma in uno stato indeterminato.
Se non si desidera chiamare il metodo per attendere il Task.Wait completamento di un'attività, è anche possibile recuperare l'eccezione AggregateException dalla proprietà dell'attività Exception , come illustrato nell'esempio seguente. Per ulteriori informazioni, vedere la sezione Osservare le eccezioni utilizzando la proprietà Task.Exception in questo contesto.
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!
Attenzione
Il codice di esempio precedente include un while ciclo che controlla la proprietà dell'attività Task.IsCompleted per determinare quando l'attività è stata completata. Questa operazione non deve mai essere eseguita nel codice di produzione perché è molto inefficiente.
Se non si attende un'attività che propaga un'eccezione o si accede alla relativa proprietà Exception, l'eccezione viene escalata in base ai criteri di eccezioni di .NET quando l'attività viene sottoposta al processo di Garbage Collection.
Quando le eccezioni possono risalire fino al thread di join, è possibile che un'attività continui a elaborare alcuni elementi dopo che l'eccezione è stata sollevata.
Nota
Quando "Just My Code" è abilitato, Visual Studio in alcuni casi si interromperà sulla riga che genera l'eccezione e visualizza un messaggio di errore che indica che l'eccezione non è gestita dal codice utente. Questo errore non è dannoso. È possibile premere F5 per continuare e visualizzare il comportamento di gestione delle eccezioni illustrato in questi esempi. Per impedire l'interruzione di Visual Studio al primo errore, deselezionare la casella di controllo Abilita Just My Code in Strumenti, Opzioni, Debug, Generale.
Attività figlio associate e eccezioni aggregate annidate
Se un'attività ha un'attività figlia associata che genera un'eccezione, tale eccezione viene incapsulata in un oggetto AggregateException prima che venga propagata all'attività padre, che incapsula tale eccezione nel proprio AggregateException prima di propagarla nuovamente al thread chiamante. In questi casi, la proprietà InnerExceptions dell'eccezione AggregateException intercettata nel metodo Task.Wait, WaitAny o WaitAll contiene una o più AggregateException istanze, non le eccezioni originali che hanno causato il problema. Per evitare di dover eseguire l'iterazione sulle eccezioni annidate AggregateException , è possibile usare il Flatten metodo per rimuovere tutte le eccezioni annidate AggregateException , in modo che la AggregateException.InnerExceptions proprietà contenga le eccezioni originali. Nell'esempio seguente, le istanze annidate AggregateException vengono appiattite e gestite in un solo ciclo.
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.
È possibile utilizzare anche il metodo AggregateException.Flatten per rilanciare le eccezioni interne dalle istanze generate da più attività AggregateException in una singola istanza AggregateException, come mostrato nell'esempio seguente.
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.
Eccezioni dalle attività figlio scollegate
Per impostazione predefinita, le attività figlio vengono create come scollegate. Le eccezioni generate dalle attività scollegate devono essere gestite o rigenerate nell'attività padre immediata; non vengono propagate nuovamente al thread chiamante nello stesso modo in cui le attività figlio collegate vengono propagate. Il genitore superiore può rilanciare manualmente un'eccezione da un figlio scollegato perché venga incapsulata in un AggregateException e propagata nuovamente al thread chiamante.
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.
Anche se si usa una continuazione per osservare un'eccezione in un'attività figlia, l'eccezione deve comunque essere osservata dall'attività padre.
Eccezioni che indicano l'annullamento cooperativo
Quando il codice utente in un'attività risponde a una richiesta di annullamento, la procedura corretta consiste nel generare un OperationCanceledException passando il token di annullamento su cui è stata comunicata la richiesta. Prima di tentare di propagare l'eccezione, l'istanza dell'attività confronta il token nell'eccezione con quello passato al momento della creazione. Se sono identiche, l'attività avvolge un TaskCanceledException nel AggregateException, e questo può essere visualizzato quando vengono esaminate le eccezioni interne. Tuttavia, se il thread chiamante non è in attesa dell'attività, questa specifica eccezione non verrà propagata. Per altre informazioni, vedere Annullamento attività .
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 metodo handle per filtrare le eccezioni interne
È possibile usare il AggregateException.Handle metodo per filtrare le eccezioni che è possibile considerare come "gestite" senza usare altre logiche. Nel delegato utente fornito al metodo AggregateException.Handle(Func<Exception,Boolean>), è possibile esaminare il tipo di eccezione, la proprietà Message o qualsiasi altra informazione su di essa che consenta di determinare se è innocua. Qualsiasi eccezione per la quale il delegato restituisce false viene rigenerata in una nuova istanza di AggregateException immediatamente dopo che il metodo AggregateException.Handle è stato restituito.
L'esempio seguente è funzionalmente equivalente al primo esempio di questo argomento, che esamina ogni eccezione nella AggregateException.InnerExceptions raccolta. Invece, questo gestore di eccezioni chiama il metodo oggetto AggregateException.Handle per ogni eccezione e rilancia solo le eccezioni che non sono istanze di 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!
Di seguito è riportato un esempio più completo che usa il AggregateException.Handle metodo per fornire una gestione speciale per un'eccezione UnauthorizedAccessException durante l'enumerazione dei file.
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.
Osservazione delle eccezioni tramite la proprietà Task.Exception
Se un'attività viene completata nello stato TaskStatus.Faulted, è possibile esaminare la proprietà Exception per individuare quale specifica eccezione ha causato il guasto. Un buon modo per osservare la Exception proprietà consiste nell'usare una continuazione che viene eseguita solo se l'attività precedente ha errori, come illustrato nell'esempio seguente.
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.
In un'applicazione significativa, il delegato di continuazione potrebbe registrare informazioni dettagliate sull'eccezione e potrebbe anche generare nuove attività per riprendersi dall'eccezione. In caso di errori di un'attività, le espressioni seguenti generano l'eccezione:
await tasktask.Wait()task.Resulttask.GetAwaiter().GetResult()
Usare un'istruzione try-catch per gestire e osservare le eccezioni generate. In alternativa, osservare l'eccezione accedendo alla Task.Exception proprietà .
Importante
Non può essere intercettato AggregateException in modo esplicito quando si usano le seguenti espressioni:
await tasktask.GetAwaiter().GetResult()
Evento UnobservedTaskException
In alcuni scenari, ad esempio quando si ospitano plug-in non attendibili, le eccezioni non dannose potrebbero essere comuni e potrebbe essere troppo difficile osservarle manualmente. In questi casi, è possibile gestire l'evento TaskScheduler.UnobservedTaskException . L'istanza System.Threading.Tasks.UnobservedTaskExceptionEventArgs passata al gestore può essere usata per impedire la propagazione dell'eccezione non osservata al thread di join.