Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Använd await som standard.
await ger dig naturligt undantagsflöde, håller koden läsbar och undviker dödlägen för synkronisering över asynkronisering.
Ibland behöver du fortfarande blockera på en Task, till exempel i äldre synkrona startpunkter. I sådana fall måste du förstå hur varje API innehåller undantag.
Jämför undantagsspridning för blockerande API:er
När du måste blockera en uppgift använder du GetAwaiter(). GetResult() för att bevara den ursprungliga undantagstypen:
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 och Wait omsluta undantag i AggregateException, vilket komplicerar undantagshanteringen. Följande kod använder dessa API:er och tar emot fel undantagstyp:
// ⚠️ 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
För uppgifter som har fel med flera undantag, GetAwaiter().GetResult() kastar fortfarande ett undantag, men Task.Exception lagrar en AggregateException som innehåller alla inre undantag:
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 jämfört med GetAwaiter().GetResult()
Använd den här vägledningen när du väljer mellan de två API:erna:
- Föredrar
awaitnär du kan. Det undviker blockerings- och dödlägesrisk. - Om du måste blockera och vill ha ursprungliga undantagstyper använder du
GetAwaiter().GetResult(). Observera avsnittet Vanliga fallgropar och dödlägen i artikeln om händelsehanterare i WinForms-program. - Om din befintliga kod förväntar sig AggregateException, använd
ResultellerWait()och inspekteraInnerExceptions.
Dessa regler påverkar endast undantagsformen. Båda API:erna blockerar fortfarande den aktuella tråden, så båda kan vara låsta i entrådade SynchronizationContext miljöer. Information om hur du slutför aktiviteter korrekt på alla kodsökvägar finns i Slutför dina uppgifter.
Undantag för oobserverade aktiviteter i moderna .NET
Körmiljön utlöser TaskScheduler.UnobservedTaskException när en felande Task avslutas innan koden observerar dess undantag.
I moderna .NET kraschar inte längre ej observerade undantag processen som standard. Exekveringen rapporterar dem via en händelse och fortsätter sedan exekveringen.
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
Använd händelsen för diagnostik och telemetri. Använd inte händelsen som ersättning för normal undantagshantering i asynkrona flöden.