Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Async/await vereenvoudigt asynchrone programmering, maar bepaalde fouten worden herhaaldelijk weergegeven. In dit artikel worden de vijf meest voorkomende fouten in asynchrone code beschreven en wordt beschreven hoe u deze kunt oplossen.
De Async-methode wordt synchroon uitgevoerd
Als u het async trefwoord aan een methode toevoegt, wordt de methode niet uitgevoerd op een achtergrondthread. Het geeft aan de compiler door dat await in de methodebody mag staan en dat de retourwaarde moet worden verpakt in een Task. Wanneer u een asynchrone methode aanroept, wordt deze synchroon uitgevoerd totdat de eerste await op een onvolledig wachtbaar object is bereikt. Als de methode geen await expressies bevat of als alle awaitables al zijn voltooid, voltooit de methode volledig op de aanroepende thread.
public static class SyncExecutionExample
{
public static Task<int> ComputeAsync()
{
// No await in this method — it runs entirely synchronously.
return Task.FromResult(42);
}
}
Public Module SyncExecutionExample
Public Function ComputeAsync() As Task(Of Integer)
' No Await in this method — it runs entirely synchronously.
Return Task.FromResult(42)
End Function
End Module
Hier retourneert de methode onmiddellijk een voltooide taak, omdat deze nooit oplevert. De compiler verzendt een waarschuwing wanneer een asynchrone methode geen expressies await bevat.
Als u CPU-gebonden werk wilt verplaatsen naar een thread in de threadgroep, gebruikt u Run in plaats van async.
public static class OffloadExample
{
public static int ComputeIntensive()
{
int sum = 0;
for (int i = 0; i < 1_000; i++)
sum += i;
return sum;
}
public static Task<int> ComputeOnThreadPoolAsync()
{
return Task.Run(() => ComputeIntensive());
}
}
Public Module OffloadExample
Public Function ComputeIntensive() As Integer
Dim sum As Integer = 0
For i As Integer = 0 To 999
sum += i
Next
Return sum
End Function
Public Function ComputeOnThreadPoolAsync() As Task(Of Integer)
Return Task.Run(Function() ComputeIntensive())
End Function
End Module
Voor meer informatie over wanneer Task.Run gebruikt moeten worden, zie Asynchrone wrappers voor synchrone methoden.
Kan geen async void-methode afwachten
Wanneer u een synchrone voidretourmethode converteert naar asynchroon, wijzigt u het retourtype in Task. Als u het retourtype opgeeft als void, wordt de methode 'asynchrone leegte', die u niet kunt wachten:
public static class AsyncVoidExample
{
// BAD: async void — can't be awaited.
public static async void DoWorkBadAsync()
{
await Task.Delay(100);
}
// GOOD: async Task — callers can await this.
public static async Task DoWorkGoodAsync()
{
await Task.Delay(100);
}
}
Public Module AsyncVoidExample
' BAD: Async Sub — can't be awaited.
Public Async Sub DoWorkBadAsync()
Await Task.Delay(100)
End Sub
' GOOD: Async Function returning Task — callers can await this.
Public Async Function DoWorkGoodAsync() As Task
Await Task.Delay(100)
End Function
End Module
Asynchrone void-methoden dienen voor een specifiek doel: gebeurtenis-handlers op het hoogste niveau in UI-frameworks. Buiten gebeurtenis-handlers retourneert u altijd Task of Task<T> van asynchrone methoden. Asynchrone void-methoden hebben deze nadelen:
- Uitzonderingen blijven onopgemerkt. Uitzonderingen die zijn opgetreden in een asynchrone ongeldige methode worden doorgegeven aan de SynchronizationContext methode die actief was toen de methode werd gestart. De aanroeper kan deze uitzonderingen niet ondervangen.
- Bellers kunnen de voltooiing niet volgen. Zonder een
Task, is er geen mechanisme om te weten wanneer de bewerking is voltooid. - Testen is moeilijk. U kunt in een test de methode niet afwachten om het gedrag ervan te verifiëren.
Impasses blokkeren voor asynchrone code
Deze fout is de meest voorkomende oorzaak van asynchrone code die nooit is voltooid. Dit gebeurt wanneer u synchroon blokkeert (aanroep Wait, Task<TResult>.Resultof GetAwaiter.GetResult) op een thread met één thread SynchronizationContext.
De volgorde die een impasse veroorzaakt:
- Code op de UI-thread (of een ASP.NET aanvraagthread in oudere ASP.NET) roept een asynchrone methode aan en blokkeert voor de geretourneerde taak.
- De asynchrone methode wacht op een onvolledige taak zonder
ConfigureAwait(false)te gebruiken. - Wanneer de verwachte taak is voltooid, probeert de voortzetting terug te sturen naar het oorspronkelijke
SynchronizationContext. - De thread van die context wordt geblokkeerd totdat de taak is voltooid. Dit is een impasse.
public static class DeadlockExample
{
public static async Task<string> GetDataAsync()
{
// Without ConfigureAwait(false), this continuation
// posts back to the original SynchronizationContext.
await Task.Delay(100);
return "data";
}
public static void CallerThatDeadlocks()
{
// On a single-threaded SynchronizationContext (e.g. UI thread),
// the following line deadlocks because the continuation needs
// the same thread that .Result is blocking.
string result = GetDataAsync().Result;
}
}
Public Module DeadlockExample
Public Async Function GetDataAsync() As Task(Of String)
' Without ConfigureAwait(False), this continuation
' posts back to the original SynchronizationContext.
Await Task.Delay(100)
Return "data"
End Function
Public Sub CallerThatDeadlocks()
' On a single-threaded SynchronizationContext (e.g. UI thread),
' the following line deadlocks because the continuation needs
' the same thread that .Result is blocking.
Dim result As String = GetDataAsync().Result
End Sub
End Module
Impasses voorkomen
Gebruik een of meer van deze strategieën:
Niet blokkeren. Gebruiken
awaitin plaats van.Result:.Wait()public static class DeadlockFix1 { public static async Task CallerFixedAsync() { // Use await instead of .Result string result = await DeadlockExample.GetDataAsync(); Console.WriteLine(result); } }Public Module DeadlockFix1 Public Async Function CallerFixedAsync() As Task ' Use Await instead of .Result Dim result As String = Await DeadlockExample.GetDataAsync() Console.WriteLine(result) End Function End ModuleGebruik
ConfigureAwait(false)in bibliotheekcode. Wanneer uw bibliotheekmethode niet hoeft te worden hervat in de context van de aanroeper, specificeert uConfigureAwait(false)voor elkeawait.public static class DeadlockFix2 { public static async Task<string> GetDataSafeAsync() { await Task.Delay(100).ConfigureAwait(false); return "data"; } }Public Module DeadlockFix2 Public Async Function GetDataSafeAsync() As Task(Of String) Await Task.Delay(100).ConfigureAwait(False) Return "data" End Function End ModuleDoor
ConfigureAwait(false)te gebruiken vertelt men de runtime de voortzetting niet terug te leiden naar het oorspronkelijkeSynchronizationContext. Deze aanpak beschermt bellers die blokkeren en verbetert de prestaties door onnodige threadhops te voorkomen.
Waarschuwing
Statische constructor deadlock. De CLR heeft een vergrendeling tijdens het uitvoeren van statische constructors (cctors). Als een statische constructor op een taak blokkeert en de voortzetting van die taak code moet uitvoeren in hetzelfde type (of een type dat betrokken is bij de constructieketen), kan de voortzetting niet doorgaan omdat de cctor-vergrendeling vastgehouden wordt. Voorkom het volledig blokkeren van aanroepen binnen statische constructors.
Taaktaak<> uitpakken
Wanneer u een asynchrone lambda doorgeeft aan een methode zoals StartNew, is het geretourneerde object een Task<Task> (of Task<Task<TResult>>), niet een eenvoudige Task. De buitenste taak wordt voltooid zodra de asynchrone lambda de eerste opbrengst bereikt await. Er wordt niet gewacht totdat de interne taak is voltooid.
public static class TaskTaskBugExample
{
public static async Task DemoAsync()
{
var sw = Stopwatch.StartNew();
// StartNew returns Task<Task>, not Task.
// The outer task completes immediately when the lambda yields.
await Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
});
// Elapsed shows ~0 seconds, not ~1 second.
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module TaskTaskBugExample
Public Async Function DemoAsync() As Task
Dim sw = Stopwatch.StartNew()
' StartNew returns Task(Of Task), not Task.
' The outer task completes immediately when the lambda yields.
Await Task.Factory.StartNew(Async Function()
Await Task.Delay(1000)
End Function)
' Elapsed shows ~0 seconds, not ~1 second.
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
Los dit probleem op drie manieren op:
Gebruik in plaats daarvan Run.
Task.RunpaktTask<Task>automatisch uit:public static class TaskTaskFix1 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); await Task.Run(async () => { await Task.Delay(1000); }); Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix1 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Await Task.Run(Async Function() Await Task.Delay(1000) End Function) Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End ModuleRoep Unwrap het resultaat aan:
public static class TaskTaskFix2 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); await Task.Factory.StartNew(async () => { await Task.Delay(1000); }).Unwrap(); Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix2 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Await Task.Factory.StartNew(Async Function() Await Task.Delay(1000) End Function).Unwrap() Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End ModuleWacht tweemaal (eerst de buitenste taak, vervolgens de binnenste):
public static class TaskTaskFix3 { public static async Task DemoAsync() { var sw = Stopwatch.StartNew(); Task<Task> outerTask = Task.Factory.StartNew(async () => { await Task.Delay(1000); }); Task innerTask = await outerTask; await innerTask; Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s"); } }Public Module TaskTaskFix3 Public Async Function DemoAsync() As Task Dim sw = Stopwatch.StartNew() Dim outerTask As Task(Of Task) = Task.Factory.StartNew(Async Function() Await Task.Delay(1000) End Function) Dim innerTask As Task = Await outerTask Await innerTask Console.WriteLine($"Elapsed: {sw.Elapsed.TotalSeconds:F2}s") End Function End Module
Ontbrekende wacht op een aanroep voor het retourneren van taken
Als u een methode voor het retourneren van taken aanroept in een async methode zonder erop te wachten, start de methode een asynchrone bewerking, maar wacht niet totdat deze is voltooid. De compiler waarschuwt u voor dit geval met CS4014 in C# en BC42358 in Visual Basic:
public static class MissingAwaitExample
{
// BAD: Task.Delay is started but never awaited.
public static async Task PauseOneSecondBuggyAsync()
{
Task.Delay(1000); // CS4014 warning
}
// GOOD: await the task.
public static async Task PauseOneSecondAsync()
{
await Task.Delay(1000);
}
}
Public Module MissingAwaitExample
' BAD: Task.Delay is started but never awaited.
Public Async Function PauseOneSecondBuggyAsync() As Task
Task.Delay(1000) ' Warning BC42358
End Function
' GOOD: Await the task.
Public Async Function PauseOneSecondAsync() As Task
Await Task.Delay(1000)
End Function
End Module
Als u het resultaat opslaat in een variabele, wordt de waarschuwing onderdrukt, maar wordt de onderliggende fout niet opgelost. Altijd await de taak uitvoeren, tenzij u specifiek het "fire-and-forget"-gedrag wilt gebruiken.