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 szinkron metódussal rendelkezik egy könyvtárban, előfordulhat, hogy szeretne egy aszinkron megfelelő verziót elérhetővé tenni, amely azt Task.Run-ba burkolja.
public T Foo() { /* synchronous work */ }
// Don't do this in a library:
public Task<T> FooAsync()
{
return Task.Run(() => Foo());
}
Ez a cikk azt ismerteti, hogy ez a megközelítés miért szinte mindig helytelen a könyvtárak számára, és hogyan kell gondolni a kompromisszumokra.
Méretezhetőség vs. kiszervezés
Az aszinkron programozás két különböző előnnyel jár:
- Méretezhetőség – Csökkentheti az erőforrás-felhasználást a szálak felszabadításával az I/O-várakozások során.
- Munka áthelyezése szálra – A munka áthelyezése másik szálra a válaszkészség fenntartása érdekében (például a felhasználói felület szálának szabadon tartása) vagy a párhuzam elérése érdekében.
Ezek az előnyök különböző megközelítéseket igényelnek. A kritikus különbség: a szinkron metódus Task.Run burkolása segít a feladatátadásban, de nem tesz semmit a méretezhetőséghez.
Miért Task.Run nem javítja a méretezhetőséget?
A valóban aszinkron implementáció csökkenti a hosszú ideig futó művelet során felhasznált szálak számát. A Task.Run burkoló továbbra is blokkolja a szálakat – csak áthelyezi a blokkolást az egyik szálról a másikra:
public static class TimerExampleWrong
{
public static Task SleepAsync(int millisecondsTimeout)
{
return Task.Run(() => Thread.Sleep(millisecondsTimeout));
}
}
Public Module TimerExampleWrong
Public Function SleepAsync(millisecondsTimeout As Integer) As Task
Return Task.Run(Sub() Thread.Sleep(millisecondsTimeout))
End Function
End Module
Hasonlítsa össze ezt a megközelítést egy valóban aszinkron implementációval, amely várakozás közben nem használ szálakat:
public static class TimerExampleRight
{
public static Task SleepAsync(int millisecondsTimeout)
{
var tcs = new TaskCompletionSource<bool>();
var timer = new Timer(
_ => tcs.TrySetResult(true), null, millisecondsTimeout, Timeout.Infinite);
tcs.Task.ContinueWith(
_ => timer.Dispose(), TaskScheduler.Default);
return tcs.Task;
}
}
Public Module TimerExampleRight
Public Function SleepAsync(millisecondsTimeout As Integer) As Task
Dim tcs As New TaskCompletionSource(Of Boolean)()
Dim tmr As New Timer(
Sub(state) tcs.TrySetResult(True), Nothing, millisecondsTimeout, Timeout.Infinite)
tcs.Task.ContinueWith(
Sub(t) tmr.Dispose(), TaskScheduler.Default)
Return tcs.Task
End Function
End Module
Mindkét implementáció befejeződik a megadott késleltetés után, de a második implementáció nem blokkolja a szálat várakozás közben. A több egyidejű kérést kezelő kiszolgálóalkalmazások esetében ez a különbség közvetlenül befolyásolja, hogy egy kiszolgáló hány kérést tud egyszerre feldolgozni.
Az átterhelés a fogyasztó felelőssége
Szinkron hívások Task.Run keretben történő végrehajtása hasznos lehet a UI szálról való munka áthelyezésére. A fogyasztónak, és nem a kódtárnak, kell kezelnie ezt a csomagolást:
public static class UIOffloadExample
{
public static int ComputeIntensive(int input)
{
int result = 0;
for (int i = 0; i < input; i++)
{
result += i;
}
return result;
}
public static async Task ConsumeFromUIThreadAsync()
{
int result = await Task.Run(() => ComputeIntensive(10_000));
Console.WriteLine($"Result: {result}");
}
}
Public Module UIOffloadExample
Public Function ComputeIntensive(input As Integer) As Integer
Dim result As Integer = 0
For i As Integer = 0 To input - 1
result += i
Next
Return result
End Function
Public Async Function ConsumeFromUIThreadAsync() As Task
Dim result As Integer = Await Task.Run(Function() ComputeIntensive(10_000))
Console.WriteLine($"Result: {result}")
End Function
End Module
A fogyasztó ismeri a környezetét: hogy UI szálon fut-e, mennyi részletességre van szüksége, és hogy a leterhelés hozzáadott értéket képvisel-e. A könyvtár nem működik.
Miért nem szabad a könyvtáraknak elérhetővé tenni az aszinkron-szinkron burkolókat?
Ha egy kódtár csak a szinkron metódust teszi elérhetővé (és nem aszinkron burkolót), a fogyasztók többféleképpen is profitálnak:
- Csökkentett API-felület: Kevesebb tanulásra, tesztelésre és karbantartásra használható módszer.
- Nincs félrevezető méretezhetőségi elvárás: A felhasználók tudják, hogy csak az aszinkronként közzétett módszerek biztosítják a méretezhetőség előnyeit.
-
Fogyasztói ellenőrzés: A hívók eldöntik, hogy kiszerveznek-e, és hogyan valósítják meg, a megfelelő részletességi szinten. A nagy átviteli sebességű kiszolgálóalkalmazások közvetlenül meghívhatják a szinkron metódust, így elkerülhető a felesleges többletterhelés.
Task.Run - Jobb teljesítmény: Az aszinkron burkolók terhelést okoznak a foglalások, a környezeti kapcsolók és a szálkészlet ütemezése révén. A részletes műveletek esetében ez a többletterhelés jelentős lehet.
A szabály alóli kivételek
Egyes alaposztályok az aszinkron metódusokat teszik elérhetővé, hogy a származtatott osztályok valóban aszinkron implementációkkal felülbírálhassák őket. Az alaposztály alapértelmezett aszinkron szinkronizálást biztosít.
Például elérhetővé Stream teszi ReadAsync és WriteAsync. Az alap implementációk a szinkron Read és Write a metódusokat burkolják. A származtatott osztályok, mint a FileStream és NetworkStream, felülbírálják ezeket a metódusokat aszinkron I/O-implementációkkal, amelyek valós méretezhetőségi előnyöket biztosítanak.
Hasonlóképpen, TextReader burkolóként biztosítja a ReadToEndAsync az alaposztályon, és StreamReader felülbírálja azt egy valóban aszinkron implementációval, amely belülről hív egy ReadAsync-t.
Ezek a kivételek a következők miatt érvényesek:
- A minta polimorfizmusra lett tervezve. A hívók az alaptípussal kommunikálnak.
- A származtatott típusok valóban aszinkron felülbírálásokat biztosítanak.
Irányelv
Csak akkor tegye elérhetővé az aszinkron metódusokat egy könyvtárból, ha az implementáció valós méretezhetőségi előnyöket biztosít a szinkron megfelelőjéhez képest. Ne tegye közzé az aszinkron metódusokat kizárólag átterhelés céljából. Ezt a döntést a fogyasztóra bízza.