Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Verwenden Sie await als Standard.
await bietet einen natürlichen Ausnahmefluss, trägt zur Lesbarkeit Ihres Codes bei und vermeidet Sync-über-Async-Deadlocks.
Manchmal müssen Sie immer noch auf einem Task, z. B. in älteren synchronen Einstiegspunkten, blockieren. In diesen Fällen müssen Sie verstehen, wie die einzelnen APIs Ausnahmen handhaben.
Vergleich der Ausbreitung von Ausnahmen bei blockierenden APIs
Wenn Sie eine Aufgabe blockieren müssen, verwenden Sie GetAwaiter(). GetResult() zum Beibehalten des ursprünglichen Ausnahmetyps:
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 und Wait umschließen Ausnahmen in AggregateException, wodurch die Ausnahmebehandlung erschwert wird. Der folgende Code verwendet diese APIs und empfängt den falschen Ausnahmetyp:
// ⚠️ 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
Bei Vorgängen, die mit mehreren Ausnahmen fehlschlagen, löst GetAwaiter().GetResult() weiterhin eine Ausnahme aus, jedoch speichert Task.Exception eine AggregateException, die alle inneren Ausnahmen enthält.
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
Task.Result Vs GetAwaiter().GetResult()
Verwenden Sie diese Anleitung, wenn Sie zwischen den beiden APIs wählen:
- Bevorzugen Sie
await, wenn Sie können. Dadurch werden Blockierung und Deadlock-Risiko vermieden. - Wenn Sie blockieren müssen und die ursprünglichen Ausnahmetypen beibehalten möchten, verwenden Sie
GetAwaiter().GetResult(). Beachten Sie in WinForms-Anwendungen den Abschnitt "Häufige Stolpersteine" und "Deadlocks" des Artikels zu Ereignishandlern. - Wenn Ihr vorhandener Code AggregateException erwartet, verwenden Sie
ResultoderWait()und prüfen SieInnerExceptions.
Diese Regeln wirken sich nur auf die Ausnahmeform aus. Beide APIs blockieren den aktuellen Thread weiterhin, sodass beide in Singlethread-Umgebungen deadlocken SynchronizationContext können. Informationen zum ordnungsgemäßen Ausführen von Aufgaben in allen Codepfaden finden Sie unter "Ausführen Ihrer Aufgaben".
Ausnahmen für nicht überwachte Aufgaben in modernen .NET
Die Laufzeit löst TaskScheduler.UnobservedTaskException aus, wenn ein fehlerhaftes Task abgeschlossen wird, bevor der Code seine Ausnahme erfasst.
In aktuellen .NET-Versionen stürzt der Prozess standardmäßig nicht mehr aufgrund nicht beobachteter Ausnahmen ab. Die Laufzeit meldet sie über das Ereignis und setzt dann die Ausführung fort.
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
Verwenden Sie das Ereignis für Diagnose und Telemetrie. Verwenden Sie dieses Ereignis nicht als Ersatz für die normale Ausnahmebehandlung in asynchronen Abläufen.