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.
Usare await come impostazione predefinita.
await offre un flusso di eccezioni naturale, mantiene il codice leggibile ed evita deadlock sincroni.
A volte è necessario bloccare su un Taskoggetto, ad esempio, nei punti di ingresso sincroni legacy. In questi casi, è necessario comprendere in che modo ogni API presenta le eccezioni.
Confrontare la propagazione delle eccezioni per le API di blocco
Quando è necessario bloccare un'attività, usa GetAwaiter().GetResult() per mantenere il tipo di eccezione originale.
public static class SingleExceptionExample
{
public static Task<int> FaultAsync()
{
return Task.FromException<int>(new InvalidOperationException("Single failure"));
}
public static void ShowBlockingDifferences()
{
try
{
_ = FaultAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine($"GetAwaiter().GetResult() threw {ex.GetType().Name}");
}
}
}
Public Module SingleExceptionExample
Public Function FaultAsync() As Task(Of Integer)
Return Task.FromException(Of Integer)(New InvalidOperationException("Single failure"))
End Function
Public Sub ShowBlockingDifferences()
Try
Dim ignored = FaultAsync().GetAwaiter().GetResult()
Catch ex As Exception
Console.WriteLine($"GetAwaiter().GetResult() threw {ex.GetType().Name}")
End Try
End Sub
End Module
Task<TResult>.Result e Wait incapsulano le eccezioni in AggregateException, il che complica la gestione delle eccezioni. Il codice seguente usa queste API e riceve il tipo di eccezione errato:
// ⚠️ DON'T copy this snippet. It demonstrates a problem where exceptions get wrapped unnecessarily.
public static class SingleExceptionBadExample
{
public static Task<int> FaultAsync()
{
return Task.FromException<int>(new InvalidOperationException("Single failure"));
}
public static void ShowBlockingDifferences()
{
try
{
_ = FaultAsync().Result;
}
catch (AggregateException ex)
{
Console.WriteLine($".Result threw {ex.GetType().Name} with inner {ex.InnerException?.GetType().Name}");
}
}
}
' ⚠️ DON'T copy this snippet. It demonstrates a problem where exceptions get wrapped unnecessarily.
Public Module SingleExceptionBadExample
Public Function FaultAsync() As Task(Of Integer)
Return Task.FromException(Of Integer)(New InvalidOperationException("Single failure"))
End Function
Public Sub ShowBlockingDifferences()
Try
Dim ignored = FaultAsync().Result
Catch ex As AggregateException
Console.WriteLine($".Result threw {ex.GetType().Name} with inner {ex.InnerException?.GetType().Name}")
End Try
End Sub
End Module
Per le attività con errori con più eccezioni, GetAwaiter().GetResult() genera comunque un'eccezione, ma Task.Exception archivia un oggetto AggregateException che contiene tutte le eccezioni interne:
public static class MultiExceptionExample
{
public static async Task FaultAfterDelayAsync(string name, int milliseconds)
{
await Task.Delay(milliseconds);
throw new InvalidOperationException($"{name} failed");
}
public static void ShowMultipleExceptions()
{
Task combined = Task.WhenAll(
FaultAfterDelayAsync("First", 10),
FaultAfterDelayAsync("Second", 20));
try
{
combined.GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine($"GetAwaiter().GetResult() surfaced: {ex.Message}");
}
if (combined.IsFaulted && combined.Exception is not null)
{
AggregateException allErrors = combined.Exception.Flatten();
Console.WriteLine($"Task.Exception contains {allErrors.InnerExceptions.Count} exceptions.");
}
else
{
Console.WriteLine("Task.Exception is null because the task didn't fault.");
}
}
}
Public Module MultiExceptionExample
Public Async Function FaultAfterDelayAsync(name As String, milliseconds As Integer) As Task
Await Task.Delay(milliseconds)
Throw New InvalidOperationException($"{name} failed")
End Function
Public Sub ShowMultipleExceptions()
Dim combined As Task = Task.WhenAll(
FaultAfterDelayAsync("First", 10),
FaultAfterDelayAsync("Second", 20))
Try
combined.GetAwaiter().GetResult()
Catch ex As Exception
Console.WriteLine($"GetAwaiter().GetResult() surfaced: {ex.Message}")
End Try
If combined.IsFaulted AndAlso combined.Exception IsNot Nothing Then
Dim allErrors As AggregateException = combined.Exception.Flatten()
Console.WriteLine($"Task.Exception contains {allErrors.InnerExceptions.Count} exceptions.")
Else
Console.WriteLine("Task.Exception was not available because the task did not fault.")
End If
End Sub
End Module
Confronto tra Task.Result e GetAwaiter().GetResult()
Usare queste indicazioni quando si sceglie tra le due API:
- Preferisci
awaitquando puoi. Evita il rischio di blocco e deadlock. - Se è necessario bloccare e si vogliono tipi di eccezione originali, usare
GetAwaiter().GetResult(). Nelle applicazioni WinForms prendere nota della sezione Common pitfalls and deadlocks (Errori comuni e deadlock ) dell'articolo sui gestori eventi. - Se il codice esistente prevede AggregateException, usare
ResultoWait()ed esaminareInnerExceptions.
Queste regole influiscono solo sulla forma delle eccezioni. Entrambe le API bloccano ancora il thread corrente, in modo che entrambi possano eseguire il deadlock in ambienti a SynchronizationContext thread singolo. Per informazioni su come completare correttamente le attività in tutti i percorsi di codice, vedere Completare le attività.
Eccezioni di attività non rilevate nelle .NET moderne
Il runtime genera TaskScheduler.UnobservedTaskException quando un errore Task viene finalizzato prima che il codice osservi l'eccezione.
Nelle versioni moderne di .NET, le eccezioni non rilevate non bloccano più il processo per impostazione predefinita. Il runtime li segnala tramite l'evento e quindi continua l'esecuzione.
public static class UnobservedTaskExceptionExample
{
public static void ShowEventBehavior()
{
bool eventRaised = false;
TaskScheduler.UnobservedTaskException += (_, args) =>
{
eventRaised = true;
Console.WriteLine($"UnobservedTaskException raised with {args.Exception.InnerExceptions.Count} exception(s).");
args.SetObserved();
};
_ = Task.Run(() => throw new ApplicationException("Background failure"));
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(eventRaised
? "Event was raised. The process continued."
: "Event was not observed in this short run. The process still continued.");
}
}
Public Module UnobservedTaskExceptionExample
Public Sub ShowEventBehavior()
Dim eventRaised As Boolean = False
AddHandler TaskScheduler.UnobservedTaskException,
Sub(sender, args)
eventRaised = True
Console.WriteLine($"UnobservedTaskException raised with {args.Exception.InnerExceptions.Count} exception(s).")
args.SetObserved()
End Sub
Task.Run(Sub() Throw New ApplicationException("Background failure"))
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
If eventRaised Then
Console.WriteLine("Event was raised. The process continued.")
Else
Console.WriteLine("Event was not observed in this short run. The process still continued.")
End If
End Sub
End Module
Usare l'evento per la diagnostica e i dati di telemetria. Non usare l'evento come sostituzione per la normale gestione delle eccezioni nei flussi asincroni.