Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az Async/await leegyszerűsíti az aszinkron programozást, de bizonyos hibák ismételten megjelennek. Ez a cikk az aszinkron kód öt leggyakoribb hibáját ismerteti, és bemutatja, hogyan lehet kijavítani őket.
Az Async metódus szinkron módon fut
async A kulcsszó metódushoz való hozzáadása nem teszi lehetővé, hogy a metódus háttérszálon fusson. Arra utasítja a fordítót, hogy engedélyezze await a metódus törzsén belül, és a visszatérési értéket majd csomagolja egy Task-be. Amikor meghív egy aszinkron metódust, az szinkron módon fut, amíg egy nem befejezett várakoztatón el nem éri az első await -t. Ha a metódus nem tartalmaz await kifejezéseket, vagy ha minden várakoztatható objektum, amelyre várakozik, már befejeződött, akkor a metódus teljes egészében a hívó szálon fejeződik be.
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
Itt a metódus azonnal visszaad egy befejezett feladatot, mert soha nem ad eredményül. A fordító figyelmeztetést ad ki, ha egy aszinkron metódus nem tartalmaz await kifejezéseket.
Ha a cél a CPU-kötött munka kiszervezése a szálkészlet egy szálára, használja Run a async helyett.
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
A Task.Run használatának idejére vonatkozó további útmutatásért tekintse meg az Aszinkron burkolókat a szinkron metódusokhoz.
Nem lehet várni az aszinkron void metódust
Ha szinkronizált void-returning metódust aszinkronra konvertál, módosítsa a visszatérési típust a következőre Task: . Ha a visszatérési típust void-ként hagyja meg, a metódus "aszinkron void"-dá válik, amelyre nem lehet várakozni:
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
Az aszinkron üres metódusok egy adott célt szolgálnak: a legfelső szintű eseménykezelők a felhasználói felületi keretrendszerekben. Az eseménykezelőkön kívül mindig térjen vissza Task vagy Task<T> az aszinkron metódusokból. Az aszinkron üres metódusoknak az alábbi hátrányai vannak:
- A kivételek észrevétlenek maradnak. Az aszinkron void metódusban dobott kivételek arra a környezetre propagálnak, amely aktív volt a metódus indításakor SynchronizationContext . A hívó nem tudja elkapni ezeket a kivételeket.
- A hívók nem tudják nyomon követni a befejezést. Mechanizmus hiányában nem lehet tudni, mikor fejeződik be a művelet.
- A tesztelés nehéz. Nem várhatja meg a metódust egy tesztben annak viselkedésének ellenőrzéséhez.
Holtpontok az aszinkron kód blokkolása miatt
Ez a hiba a leggyakoribb oka annak, hogy az aszinkron kód „soha nem fejeződik be”. Ez akkor fordul elő, ha szinkron módon blokkol (hív Wait, Task<TResult>.Result, vagy GetAwaiter.GetResult) egy egyszálas SynchronizationContext szálon.
A holtpontot okozó sorrend:
- A felhasználói felületi szál kódja (vagy egy régebbi ASP.NET ASP.NET kérésszála) aszinkron metódust hív meg, és blokkolja a visszaadott feladatot.
- Az aszinkron módszer
ConfigureAwait(false)nélkül vár egy befejezetlen feladatra. - Amikor a várt tevékenység befejeződik, a folytatás megpróbál visszatérni az eredetihez
SynchronizationContext. - Az összefüggés szála blokkolva van, és a feladat befejezésére vár – holtpont.
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
Holtpontok elkerülése
Használjon legalább egy ilyen stratégiát:
Ne tiltsa le. Használja a(z)
await-t a(z).Resultvagy.Wait()helyett.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 ModuleKódtárkódban használható
ConfigureAwait(false). Ha a könyvtár metódusának nem kell folytatnia a hívó környezetében, adja megConfigureAwait(false)mindenawait: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 ModuleA
ConfigureAwait(false)használata jelzi a futtatókörnyezetnek, hogy ne küldje vissza a folytatást az eredetiSynchronizationContext-re. Ez a megközelítés védelmet nyújt a letiltó hívóknak, és a szükségtelen szálugraások elkerülésével javítja a teljesítményt.
Figyelmeztetés
Statikus konstruktor holtpont. A CLR statikus konstruktorok (cctorek) futtatása közben zárolást tartalmaz. Ha egy statikus konstruktor blokkol egy tevékenységet, és a tevékenység folytatásának kódot kell futtatnia ugyanabban a típusban (vagy az építési láncban részt vevő típusban), a folytatás nem folytatható, mert a cctor zárolást megtartotta. Kerülje a hívások blokkolását a statikus konstruktorokban teljes mértékben.
Tevékenység<Tevékenység> kibontása
Ha egy aszinkron lambdát ad át egy olyan metódusnak, mint a StartNew, a visszaadott objektum egy Task<Task> (vagy Task<Task<TResult>>), nem egyszerű Task. A külső feladat akkor fejeződik be, amikor az aszinkron lambda eléri első hozamát await. Nem várja meg, amíg a belső feladat befejeződik:
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
A probléma megoldása háromféleképpen lehetséges:
A Run használható helyette.
Task.Runautomatikusan kibontjaTask<Task>: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 ModuleAz eredmény meghívása Unwrap :
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 ModuleVárjon kétszer (először a külső feladat, majd a belső):
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
Hiányzó 'await' egy feladatot visszaadó hívásnál
Ha egy async metódusban feladat-visszaadó metódust hív meg anélkül, hogy megvárná, a metódus elindítja az aszinkron műveletet, de nem várja meg, hogy befejeződjön. A fordító figyelmezteti önt erre az esetre a CS4014 C# és BC42358 Visual Basic esetén:
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
Az eredmény változóban való tárolása letiltja a figyelmeztetést, de nem javítja ki a mögöttes hibát. Mindig await hajtsa végre a feladatot, hacsak nem szándékosan szeretne "fire-and-forget" módot alkalmazni.