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.
Ha egy kódtár csak aszinkron API-kat tesz elérhetővé, a felhasználók néha szinkron hívásokba csomagolják őket, hogy kielégítsék a szinkron felületet vagy szerződést. Ez a "sync-over-async" minta egyszerűnek tűnhet, de ez a holtpontok és a teljesítményproblémák gyakori forrása.
Alapszintű körbefuttatási minták
A tevékenységalapú aszinkron minta (TAP) metódus körüli szinkron burkoló hozzáfér a tevékenység tulajdonságához Result , amely blokkolja a hívó szálat:
public class TapWrapper
{
public static int Foo(Func<Task<int>> fooAsync)
{
return fooAsync().Result;
}
}
Public Module TapWrapper
Public Function Foo(fooAsync As Func(Of Task(Of Integer))) As Integer
Return fooAsync().Result
End Function
End Module
Ez a megközelítés egyszerűnek tűnik, de súlyos problémákat okozhat attól függően, hogy milyen környezetben fut.
Holtpontok egyszálas környezetben
A legveszélyesebb forgatókönyv akkor fordul elő, ha egy egyszálas SynchronizationContextszálból szinkronizált burkolót hív meg. Ez a forgatókönyv általában egy felhasználói felületi szál WPF, Windows Forms vagy MAUI-alkalmazásokban.
public static class DeadlockExample
{
private static void Delay(int milliseconds)
{
DelayAsync(milliseconds).Wait();
}
private static async Task DelayAsync(int milliseconds)
{
await Task.Delay(milliseconds);
}
}
Public Module DeadlockExample
Private Sub Delay(milliseconds As Integer)
DelayAsync(milliseconds).Wait()
End Sub
Private Async Function DelayAsync(milliseconds As Integer) As Task
Await Task.Delay(milliseconds)
End Function
End Module
Lépésről lépésre az alábbiak történnek:
- A UI szál meghívja
Delay, amely meghívjaDelayAsync(milliseconds).Wait(). -
DelayAsyncszinkron módon fut, amíg el nem ériawait Task.Delay(milliseconds). - Mivel a késés még nem fejeződött be, rögzíti az aktuális
awaitállapotot, SynchronizationContext és felfüggeszti azt.DelayAsynca hívónak ad vissza egy Task értéket. - A UI szál blokkolódik, várva arra, hogy a feladat befejeződjön
.Wait(). - Ha a késés befejeződik, a folytatásnak az eredetin
SynchronizationContextkell futnia, amely a felhasználói felület szála. - A felhasználói felületi szál nem tudja feldolgozni a folytatást, mert blokkolva van
.Wait(). - Holtpont.
Fontos
A szinkronizálási aszinkron kód sikeressége vagy sikertelensége attól függ, hogy melyik környezetben fut. A konzolalkalmazásokban működő kód holtpontot jelenthet egy felhasználói felületen vagy ASP.NET (.NET-keretrendszerben). Ez a környezeti függőség egy alapvető oka annak, hogy miért kerüljük a szinkron burkolók kitettségét.
Szálkészlet kimerülése
A holtpontok nem korlátozódnak a felhasználói felületi szálakra. Ha egy aszinkron módszer a szálkészlettől függ a munka befejezéséhez, például egy végső feldolgozási lépés sorba állításával számos készletszál szinkron burkolókkal való blokkolása éheztetheti a készletet:
public static class ThreadPoolDeadlockExample
{
public static int Foo(Func<Task<int>> fooAsync)
{
return fooAsync().Result;
}
public static async Task DemonstrateDeadlockRiskAsync()
{
var tasks = Enumerable.Range(0, 25)
.Select(_ => Task.Run(() => Foo(() => SomeIOOperationAsync())));
await Task.WhenAll(tasks);
}
private static async Task<int> SomeIOOperationAsync()
{
await Task.Delay(100);
return 42;
}
}
Ebben a forgatókönyvben:
- Sok szálkezelő készlet szál hívja a
Foo, amelyek blokkolnak a.Result. - Minden aszinkron művelet befejezi az I/O-t, és egy szálkészlet-szálra van szüksége a befejezési visszahívás futtatásához.
- Mivel a blokkolt hívások az elérhető munkaszálakat foglalják el, a befejezések hosszú ideig várhatnak, amíg elérhetővé válik egy szál.
- A modern .NET idővel több szálkészlet-szálat adhat hozzá, de az alkalmazás továbbra is súlyos szálkészlet-éhezést, gyenge átviteli sebességet, hosszú késéseket vagy látszólagos lefagyást szenvedhet.
Ez a minta HttpWebRequest.GetResponse érintett az .NET Framework 1.x-ben, ahol a szinkron metódus burkolóként lett implementálva az aszinkron BeginGetResponse/EndGetResponse köré.
Útmutató: Kerülje a szinkron burkolók felfedését
Ne tegye közzé az aszinkron implementációt burkoló szinkron metódust. Ehelyett hagyja a fogyasztóra a döntést, hogy letiltsa-e vagy sem. A fogyasztó ismeri a szálkezelés környezetét, és megalapozott döntést hozhat.
Ha úgy találja, hogy szinkron módon kell meghívnia egy aszinkron metódust, először fontolja meg, hogy át tudja-e strukturálni a kódot úgy, hogy "aszinkron legyen teljesen lefelé". Az újrabontás gyakran a jobb hosszú távú megoldás.
Kockázatcsökkentési stratégiák, ha a szinkronizálás aszinkron módon nem elkerülhetetlen
Néha az aszinkron szinkronizálás valóban elkerülhetetlen. Ez például elkerülhetetlen, ha olyan felületet implementál, amely szinkron metódust igényel, és az egyetlen elérhető implementáció az aszinkron. Ezekben az esetekben alkalmazza az alábbi stratégiákat a kockázat csökkentésére.
Használja ConfigureAwait(false) az aszinkron implementációban
Ha ön vezérli az aszinkron metódust, használja Task.ConfigureAwait minden await esetén false, hogy megakadályozza a folytatás visszairányítását az eredeti SynchronizationContext-re.
public static class ConfigureAwaitMitigation
{
public static async Task<int> LibraryMethodAsync()
{
await Task.Delay(100).ConfigureAwait(false);
return 42;
}
public static int Sync()
{
return LibraryMethodAsync().GetAwaiter().GetResult();
}
}
Public Module ConfigureAwaitMitigation
Public Async Function LibraryMethodAsync() As Task(Of Integer)
Await Task.Delay(100).ConfigureAwait(False)
Return 42
End Function
Public Function Sync() As Integer
Return LibraryMethodAsync().Result
End Function
End Module
Könyvtár készítőjeként használd a ConfigureAwait(false) minden várakozásnál, kivéve, ha a kódnak kifejezetten a rögzített környezetben kell folytatódnia. A ConfigureAwait(false) használata ajánlott eljárás a teljesítmény javítása érdekében, és segít megelőzni a holtpontokat, amikor a fogyasztók blokkolódnak.
Kiszervezés a szálkészletbe
Ha nem szabályozza az aszinkron implementációt (és lehet, hogy nem használja ConfigureAwait(false)), továbbítsa a hívást a szálkészletnek. A szálkészlet nem rendelkezik SynchronizationContext-val, ezért a várakozás nem próbálja meg visszairányítani a blokkolt szálat.
public int Sync()
{
return Task.Run(() => Library.FooAsync()).Result;
}
Public Function Sync() As Integer
Return Task.Run(Function() Library.FooAsync()).Result
End Function
Tesztelés több környezetben
Ha szinkron burkolót kell szállítania, tesztelje ilyen környezetben:
- Felhasználói felületi szál (WPF, Windows Forms).
- A szálkészlet terhelés alatt.
- Szálkészlet alacsony maximális szálszámmal.
- Egy konzolalkalmazás.
Az egyik környezetben működő viselkedés holtpontot jelenthet egy másikban.