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.
A feladatalapú aszinkron mintát (TAP) három módon implementálhatja: a C# és a Visual Basic fordítóinak használatával a Visual Studióban manuálisan, vagy a fordító és a manuális metódusok kombinációjával. Az alábbi szakaszok részletesen ismertetik az egyes metódusokat. A TAP-mintával számítási és I/O-kötésű aszinkron műveleteket is implementálhat. A Számítási feladatok szakasz az egyes műveletek típusait ismerteti.
TAP-metódusok létrehozása
A fordítók használata
A .NET-keretrendszer 4.5-től kezdődően a kulcsszóval asyncAsync (a Visual Basicben) társított metódusok aszinkron metódusnak minősülnek, a C# és a Visual Basic fordítói pedig végrehajtják a szükséges átalakításokat a metódus aszinkron implementálásához a TAP használatával. Az aszinkron metódusnak vagy System.Threading.Tasks.Task, vagy System.Threading.Tasks.Task<TResult> objektumot kell visszaadnia. Az utóbbi esetében a függvény törzsének egy értéket kell visszaadnia TResult, és a fordító biztosítja, hogy ez az eredmény elérhető legyen az eredményként kapott tevékenységobjektumon keresztül. Hasonlóképpen, a metódus törzsén belül kezeletlen kivételek a kimeneti feladathoz kerülnek továbbításra, és az eredményül kapott feladat a TaskStatus.Faulted állapotban ér véget. Ez alól a szabály alól kivételt képez, ha egy OperationCanceledException (vagy származtatott) típus kezeletlen lesz, ebben az esetben az eredményül kapott tevékenység az TaskStatus.Canceled állapotban fejeződik be.
TAP-metódusok manuális létrehozása
A TAP-mintát manuálisan is implementálhatja az implementáció jobb szabályozása érdekében. A fordító a System.Threading.Tasks névtérből látható nyilvános felületre és a System.Runtime.CompilerServices névtérben található támogató típusokra támaszkodik. A TAP saját megvalósításához hozzon létre egy TaskCompletionSource<TResult> objektumot, hajtsa végre az aszinkron műveletet, és amikor befejeződik, hívja meg az SetResult, SetExceptionvagy SetCanceled metódust vagy az Try egyik metódus verzióját. Ha manuálisan implementál egy TAP-metódust, az eredményül kapott feladatot az aszinkron művelet befejeződésekor kell elvégeznie. Például:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
<Extension()>
Public Function ReadTask(stream As Stream, buffer() As Byte,
offset As Integer, count As Integer,
state As Object) As Task(Of Integer)
Dim tcs As New TaskCompletionSource(Of Integer)()
stream.BeginRead(buffer, offset, count, Sub(ar)
Try
tcs.SetResult(stream.EndRead(ar))
Catch exc As Exception
tcs.SetException(exc)
End Try
End Sub, state)
Return tcs.Task
End Function
Hibrid megközelítés
Hasznos lehet a TAP-minta manuális implementálása, de az implementáció alapvető logikájának delegálása a fordítónak. Érdemes lehet például a hibrid megközelítést használni, ha egy fordító által létrehozott aszinkron metóduson kívül szeretné ellenőrizni az argumentumokat, hogy a kivételek ne az System.Threading.Tasks.Task objektumon keresztül, hanem a metódus közvetlen hívójához legyenek továbbítva.
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
return value;
}
Public Function MethodAsync(input As String) As Task(Of Integer)
If input Is Nothing Then Throw New ArgumentNullException("input")
Return MethodAsyncInternal(input)
End Function
Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
' code that uses await goes here
return value
End Function
Egy másik eset, amikor az ilyen delegálás hasznos, az, amikor gyors elérési út optimalizálást hajt végre, és gyorsítótárazott feladatot szeretne visszaadni.
Munkaterhek
A számítási és az I/O-kötésű aszinkron műveleteket TAP-metódusokként is implementálhatja. Ha azonban a TAP-metódusok nyilvánosan elérhetővé válnak egy tárból, csak az I/O-hez kötött műveleteket tartalmazó számítási feladatokhoz kell megadni őket (ezek számítást is magukban foglalhatnak, de nem lehetnek tisztán számítási jellegűek). Ha egy metódus tisztán számítási kötöttségű, akkor csak szinkron implementációként kell elérhetővé tenni. Az azt használó kód ezután döntheti el, hogy a szinkron metódus meghívását egy feladatba csomagolja-e, hogy a munkát egy másik szálra kicsomagolja, vagy párhuzamosságot érjen el. Ha egy metódus I/O-kötésű, akkor csak aszinkron implementációként kell elérhetővé tenni.
Számításhoz kötött tevékenységek
Az System.Threading.Tasks.Task osztály ideálisan alkalmas számítási igényű műveletek ábrázolására. Alapértelmezés szerint kihasználja az osztályon belüli speciális támogatást a ThreadPool hatékony végrehajtás érdekében, és jelentős ellenőrzést biztosít az aszinkron számítások végrehajtásakor, hol és hogyan.
A számításhoz kötött tevékenységeket a következő módokon hozhatja létre:
A .NET-keretrendszer 4.5-ös és újabb verzióiban (beleértve a .NET Core-t és a .NET 5+-ot) a statikus Task.Run metódus TaskFactory.StartNew-ként való használata egyszerűsítésként javasolt. Egyszerűen elindíthat egy szálköteg által megcélzott számítási feladatot a Run használatával. Ez a számítási feladatok indításának előnyben részesített mechanizmusa. Közvetlenül csak akkor használja
StartNew, ha részletesebben szeretné szabályozni a feladatot.A .NET-keretrendszer 4-ben használja azt a TaskFactory.StartNew metódust, amely elfogad egy delegáltat (általában egy Action<T> vagy a Func<TResult>) az aszinkron végrehajtáshoz. Ha delegáltat Action<T> ad meg, a metódus egy System.Threading.Tasks.Task olyan objektumot ad vissza, amely a meghatalmazott aszinkron végrehajtását jelöli. Ha meghatalmazottat Func<TResult> ad meg, a metódus egy objektumot System.Threading.Tasks.Task<TResult> ad vissza. A metódus overloadjai elfogadnak egy lemondási tokent (StartNew), feladatlétrehozási lehetőségeket (CancellationToken), valamint egy feladatütemezőt (TaskCreationOptions), amelyek mindegyike részletes vezérlést biztosít a feladat ütemezése és végrehajtása felett. Az aktuális feladatütemezőt megcélzó gyári példány elérhető az Factory osztály statikus tulajdonságaként (Task); például:
Task.Factory.StartNew(…).Ha külön szeretné létrehozni és ütemezni a feladatot, használja a
Tasktípus és aStartmetódus konstruktorait. A nyilvános metódusoknak csak olyan tevékenységeket szabad visszaadni, amelyek már el lettek indítva.Használja a metódus túlterhelt változatait Task.ContinueWith. Ez a metódus létrehoz egy új tevékenységet, amely egy másik tevékenység befejezésekor lesz ütemezve. Egyes túlterhelések lemondási ContinueWith jogkivonatot, folytatási lehetőségeket és feladatütemezőt fogadnak el a folytatási tevékenység ütemezésének és végrehajtásának jobb szabályozásához.
Használja a TaskFactory.ContinueWhenAll és TaskFactory.ContinueWhenAny módszereket. Ezek a metódusok létrehoznak egy új feladatot, amely akkor lesz ütemezve, amikor egy megadott tevékenységkészlet vagy bármelyik befejeződött. Ezek a módszerek túlterhelést is biztosítanak ezen tevékenységek ütemezésének és végrehajtásának szabályozásához.
A számítási feladatokban a rendszer megakadályozhatja az ütemezett tevékenységek végrehajtását, ha lemondási kérelmet kap, mielőtt elkezdené futtatni a feladatot. Ilyen esetben, ha lemondási jogkivonatot (CancellationToken objektumot) ad meg, a jogkivonatot átadhatja a jogkivonatot monitorozó aszinkron kódnak. A jogkivonatot megadhatja a korábban említett módszerek egyikének, mint például a StartNew vagy Run módszernek, hogy a Task futtatókörnyezet is figyelhesse a jogkivonatot.
Vegyünk példának egy aszinkron metódust, ami képet renderel. A feladat törzse lekérdezheti a törlési tokent, így a kód idő előtt kiléphet, ha egy törlési kérelem érkezik a renderelés során. Ezenkívül, ha a lemondási kérelem a renderelés megkezdése előtt érkezik, meg kell akadályoznia a renderelési műveletet:
internal Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
// render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As _
CancellationToken) As Task(Of Bitmap)
Return Task.Run(Function()
Dim bmp As New Bitmap(data.Width, data.Height)
For y As Integer = 0 to data.Height - 1
cancellationToken.ThrowIfCancellationRequested()
For x As Integer = 0 To data.Width - 1
' render pixel [x,y] into bmp
Next
Next
Return bmp
End Function, cancellationToken)
End Function
A számításigényes feladatok Canceled állapotban fejeződnek be, ha az alábbi feltételek közül legalább az egyik teljesül:
A lemondási kérelem az CancellationToken objektumon keresztül érkezik, amely argumentumként szolgál a létrehozási módszerhez (például
StartNewRun) mielőtt a tevékenység áttér az Running állapotra.Egy OperationCanceledException kivétel kezeletlen marad egy ilyen feladat törzsén belül; ez a kivétel ugyanazt CancellationToken tartalmazza, amelyet a feladatnak átadnak, és ez a CancellationToken jelzi, hogy törlés kérése történt.
Ha egy másik kivétel nem lesz kezelve a tevékenység törzsén belül, a tevékenység az Faulted állapotban végződik, és a tevékenységre való várakozásra vagy az eredmény elérésére tett kísérletek kivételt okoznak.
I/O-kötött tevékenységek
Olyan feladat létrehozásához, amelynek végrehajtását nem szabad folyamatosan egy szálhoz kötni, használja a TaskCompletionSource<TResult> típust. Ez a típus egy Task tulajdonságot tartalmaz, amely egy társított Task<TResult> példányt ad vissza. A feladat életciklusát olyan módszerek vezérlik TaskCompletionSource<TResult> , mint SetResulta , SetException, SetCanceledés azok TrySet változatai.
Tegyük fel, hogy olyan feladatot szeretne létrehozni, amely egy megadott idő elteltével fejeződik be. Előfordulhat például, hogy késleltetni szeretne egy tevékenységet a felhasználói felületen. Az System.Threading.Timer osztály már lehetővé teszi, hogy aszinkron módon meghívjon egy delegáltat egy meghatározott időtartam után, és a TaskCompletionSource<TResult> segítségével létrehozhat egy interfészt az időzítőhöz, például:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
TaskCompletionSource<DateTimeOffset> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(DateTimeOffset.UtcNow);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<DateTimeOffset>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset)
Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
Dim timer As Timer = Nothing
timer = New Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(DateTimeOffset.UtcNow)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
A Task.Delay metódus erre a célra van megadva, és egy másik aszinkron metóduson belül is használhatja, például egy aszinkron lekérdezési ciklus implementálásához:
public static async Task Poll(Uri url, CancellationToken cancellationToken,
IProgress<bool> progress)
{
while(true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }
progress.Report(success);
}
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken,
progress As IProgress(Of Boolean)) As Task
Do While True
Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
Dim success As Boolean = False
Try
await DownloadStringAsync(url)
success = true
Catch
' ignore errors
End Try
progress.Report(success)
Loop
End Function
Az TaskCompletionSource<TResult> osztály nem rendelkezik nem általános megfelelővel. Azonban a Task<TResult> a Task-ből származik, így használhatja az általános TaskCompletionSource<TResult> objektumot olyan I/O-kötött metódusokhoz, amelyek egyszerűen csak egy feladatot adnak vissza. Ehhez használhat egy forrást dummy TResult-val (Boolean egy jó alapértelmezett választás, de ha aggódik amiatt, hogy a felhasználó a Task-t lecímkézi egy Task<TResult> típusúra, akkor használhat magánjellegű TResult típust helyette). Az előző példában szereplő metódus például Delay az aktuális időt és az eredményül kapott eltolást (Task<DateTimeOffset>) adja vissza. Ha egy ilyen eredményérték szükségtelen, a metódus a következőképpen kódozható (figyelje meg a visszatérési típus módosítását és az argumentum TrySetResultmódosítását):
public static Task<bool> Delay(int millisecondsTimeout)
{
TaskCompletionSource<bool> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(true);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<bool>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean)
Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
Dim timer As Timer = Nothing
Timer = new Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(True)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of Boolean)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Vegyes számítási és I/O-kötött tevékenységek
Az aszinkron metódusok nem csak számítási vagy I/O-kötött műveletekre korlátozódnak, hanem a kettő keverékét is jelenthetik. Valójában több aszinkron művelet gyakran nagyobb vegyes műveletekbe van kombinálva. Az előző példában szereplő metódus például RenderAsync számításigényes műveletet hajtott végre egy kép valamilyen bemeneten imageDataalapuló rendereléséhez. Ez imageData egy olyan webszolgáltatásból származhat, amelyhez aszinkron módon fér hozzá:
public async Task<Bitmap> DownloadDataAndRenderImageAsync(
CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(
cancellationToken As CancellationToken) As Task(Of Bitmap)
Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
Return Await RenderAsync(imageData, cancellationToken)
End Function
Ez a példa azt is bemutatja, hogyan lehet egyetlen lemondási tokent több aszinkron műveleten átvezetni. További információkért tekintse meg a Feladat-alapú aszinkron minta alkalmazása című részben található lemondási útmutatót.