Share via


Feladatalapú aszinkron minta (TAP) a .NET-ben: Bevezetés és áttekintés

A .NET-ben a feladatalapú aszinkron minta az új fejlesztéshez javasolt aszinkron tervezési minta. A névtérben lévő System.Threading.Tasks és Task<TResult> aszinkron műveletek megjelenítésére használt típusokon Task alapul.

Elnevezés, paraméterek és visszatérési típusok

A TAP egyetlen módszert használ az aszinkron művelet indításának és befejezésének ábrázolására. Ez ellentétben áll az aszinkron programozási modell (APM vagy IAsyncResult) mintával és az eseményalapú aszinkron mintával (EAP). Az APM megköveteli és End metódusokat igényelBegin. Az EAP olyan metódust igényel, amelynek utótagja Async van, és egy vagy több eseményt, eseménykezelő delegálttípust és EventArg-származtatott típust is igényel. Az aszinkron metódusok a TAP-ban tartalmazzák a Async művelet neve utáni utótagot az olyan metódusok esetében, amelyek várandós típusokat adnak vissza, például Task, Task<TResult>, ValueTaskés ValueTask<TResult>. Elnevezhető GetAsyncpéldául egy aszinkron művelet, amely egy aszinkron Get értéket ad visszaTask<String>. Ha olyan osztályhoz ad hozzá TAP metódust, amely már tartalmaz egy EAP-metódusnevet az Async utótaggal, használja helyette az utótagot TaskAsync . Ha például az osztály már rendelkezik metódussal GetAsync , használja a nevet GetTaskAsync. Ha egy metódus aszinkron műveletet indít el, de nem ad vissza várandós típust, a metódus nevének a következővel Beginkell kezdődnie: , Startvagy valamilyen más ige, amely arra utal, hogy ez a metódus nem adja vissza vagy dobja el a művelet eredményét.  

A TAP metódus egy vagy egy System.Threading.Tasks.Task<TResult>értéket ad System.Threading.Tasks.Task vissza, attól függően, hogy a megfelelő szinkron metódus üres vagy egy típust TResultad vissza.

A TAP-metódus paramétereinek egyeznie kell a szinkron megfelelője paramétereivel, és ugyanabban a sorrendben kell megadni. A paraméterek azonban mentesülnek a szabály alól, outref ezért teljesen el kell kerülni. Azokat az adatokat, amelyeket egy out vagy ref több paraméteren keresztül adtunk vissza, a visszaadott Task<TResult>érték részeként TResult kell visszaadni, és egy rekord vagy egy egyéni adatstruktúrát kell használnia több érték elhelyezésére. Érdemes lehet paramétert CancellationToken hozzáadni akkor is, ha a TAP metódus szinkron megfelelője nem kínál ilyet.

A kizárólag a tevékenységek létrehozására, kezelésére vagy kombinációjára fordított metódusoknak (ha a módszer aszinkron szándéka egyértelműen szerepel a metódus nevében vagy annak a típusnak a nevében, amelyhez a metódus tartozik) nem kell követni ezt az elnevezési mintát; ezeket a módszereket gyakran kombinátoroknak nevezik. A kombinátorok közé tartoznak WhenAll például WhenAnya feladatalapú aszinkron mintát használó cikk Beépített feladatalapú kombinatorok használata című szakaszában, és ezekről is szó lesz.

Ha például a TAP szintaxis eltér az örökölt aszinkron programozási mintákban, például az Aszinkron programozási modellben (APM) és az eseményalapú aszinkron mintában (EAP) használt szintaxistól, tekintse meg az aszinkron programozási mintákat.

Aszinkron művelet kezdeményezése

A TAP-on alapuló aszinkron metódusok kis mennyiségű munkát végezhetnek szinkron módon, például az argumentumok érvényesítése és az aszinkron művelet kezdeményezése, mielőtt visszaadják az eredményül kapott feladatot. A szinkron munkát a minimálisra kell csökkenteni, hogy az aszinkron metódus gyorsan visszatérjen. A gyors visszatérés okai a következők:

  • Aszinkron metódusok hívhatók meg felhasználói felületi (UI-) szálakból, és a hosszan futó szinkron munka károsíthatja az alkalmazás válaszképességét.

  • Egyszerre több aszinkron metódus is elindítható. Ezért az aszinkron módszer szinkron részében végzett minden hosszú ideig futó munka késleltetheti az egyéb aszinkron műveletek indítását, ezáltal csökkentve az egyidejűség előnyeit.

Bizonyos esetekben a művelet végrehajtásához szükséges munka mennyisége kisebb, mint a művelet aszinkron elindításához szükséges munka mennyisége. Ilyen forgatókönyv például egy olyan adatfolyamból való olvasás, amelyben az olvasási műveletet a memóriában már pufferelt adatokkal lehet kielégíteni. Ilyen esetekben a művelet szinkron módon fejeződhet be, és egy már befejezett feladatot is visszaadhat.

Kivételek

Az aszinkron metódusnak kivételt kell kivennie az aszinkron metódushívásból, csak használati hibára válaszul. A használati hibák soha nem fordulhatnak elő az éles kódban. Ha például a metódus argumentumai ArgumentNullException egyikeként nullhivatkozást ad át (Nothinga Visual Basicben), akkor módosíthatja a hívó kódot, hogy a nullhivatkozás soha ne legyen átadva. Minden egyéb hiba esetén az aszinkron metódus futtatásakor előforduló kivételeket hozzá kell rendelni a visszaadott tevékenységhez, még akkor is, ha az aszinkron metódus szinkron módon fejeződik be a feladat visszaadása előtt. A tevékenységek általában legfeljebb egy kivételt tartalmaznak. Ha azonban a tevékenység több műveletet jelöl (például WhenAll), több kivétel is társítható egyetlen tevékenységhez.

Célkörnyezet

A TAP metódus implementálásakor meghatározhatja, hogy hol történik az aszinkron végrehajtás. Dönthet úgy, hogy végrehajtja a számítási feladatot a szálkészleten, aszinkron I/O használatával valósítja meg (anélkül, hogy a művelet végrehajtásához egy szálhoz lenne kötve), futtathatja egy adott szálon (például a felhasználói felületen), vagy bármilyen számú lehetséges környezetet használhat. Előfordulhat, hogy a TAP-metódusnak még nincs mit végrehajtania, és csak egy Task olyan feltételt adhat vissza, amely a rendszer más részén található állapot előfordulását jelzi (például egy olyan feladatot, amely egy várólistára helyezett adatstruktúrába érkező adatokat jelöl).

A TAP metódus hívója letilthatja a TAP metódus befejezésére való várakozást az eredményül kapott tevékenységre való szinkron várakozással, vagy további (folytatási) kódot futtathat az aszinkron művelet befejezésekor. A folytatási kód létrehozója szabályozza a kód végrehajtásának helyét. A folytatási kódot explicit módon, az Task osztály metódusaival (például ContinueWith) vagy implicit módon hozhatja létre a folytatásokra épülő nyelvi támogatással (például C#- await ban, Await Visual Basicben, AwaitValue F#-ban).

Tevékenység állapota

Az Task osztály egy életciklust biztosít az aszinkron műveletekhez, és ezt a ciklust az TaskStatus enumerálás képviseli. Az osztály egy metódust tesz elérhetővé, amelyből Task és azokból származó, sarokban Task<TResult>lévő típusokat támogat, és az Task építés ütemezéstől való elválasztását támogatja Start . A nyilvános Task konstruktorok által létrehozott tevékenységeket hideg tevékenységeknek nevezzük, mivel nem ütemezett Created állapotban kezdik meg az életciklusukat, és csak akkor vannak ütemezve, amikor Start ezekre a példányokra kérik őket.

Minden más tevékenység forró állapotban kezdi az életciklusát, ami azt jelenti, hogy az általuk képviselt aszinkron műveletek már elindultak, és a tevékenység állapota nem enumerálási érték TaskStatus.Created. A TAP metódusokból visszaadott összes feladatot aktiválni kell. Ha egy TAP-metódus belsőleg egy tevékenység konstruktorával hozza létre a visszaadni kívánt feladatot, a TAP metódusnak a visszaadása előtt fel kell hívnia Start az Task objektumot. A TAP metódus felhasználói nyugodtan feltételezhetik, hogy a visszaadott feladat aktív, és nem szabad megpróbálni meghívni Start a TAP metódusból visszaadott feladatokat Task . Egy aktív tevékenység meghívása Start kivételt InvalidOperationException eredményez.

Lemondás (nem kötelező)

A TAP-ban a lemondás nem kötelező mind az aszinkron metódus implementálói, mind az aszinkron metódusfelhasználók számára. Ha egy művelet engedélyezi a lemondást, az aszinkron metódus túlterhelését teszi elérhetővé, amely elfogadja a lemondási jogkivonatot (CancellationToken példányt). Konvenció szerint a paraméter neve cancellationToken.

public Task ReadAsync(byte [] buffer, int offset, int count,
                      CancellationToken cancellationToken)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
                          count As Integer,
                          cancellationToken As CancellationToken) _
                          As Task

Az aszinkron művelet figyeli ezt a jogkivonatot a lemondási kérelmek esetében. Ha lemondási kérelmet kap, dönthet úgy, hogy tiszteletben tartja a kérést, és megszakítja a műveletet. Ha a lemondási kérelem azt eredményezi, hogy a munka idő előtt véget ér, a TAP metódus egy állapotban Canceled végződő feladatot ad vissza; nincs elérhető eredmény, és nincs kivétel. Az Canceled állapotot egy tevékenység végleges (befejezett) állapotának tekintjük, az Faulted állapotokkal együtt RanToCompletion . Ezért ha egy tevékenység állapotban van, a Canceled tulajdonsága IsCompleted ad vissza true. Amikor egy tevékenység befejeződött az állapotban, a Canceled tevékenységhez regisztrált folytatások ütemezése vagy végrehajtása történik, kivéve, ha egy olyan folytatási lehetőség van megadva, amely NotOnCanceled a folytatás letiltásához van megadva. Minden olyan kód, amely aszinkron módon várakozik a megszakított feladatra a nyelvi funkciók használatával, továbbra is fut, de egy vagy egy belőle származó kivételt kap OperationCanceledException . A feladatra szinkron módon várakozó kód, például WaitWaitAll egy kivétellel továbbra is futtatható.

Ha a lemondási jogkivonat a jogkivonat meghívását elfogadó TAP metódus előtt kérte a lemondást, a TAP metódusnak egy Canceled feladatot kell visszaadnia. Ha azonban az aszinkron művelet futtatása közben lemondást kér, az aszinkron műveletnek nem kell elfogadnia a lemondási kérelmet. A visszaadott tevékenység csak akkor fejeződhet be az Canceled állapotban, ha a művelet a lemondási kérelem eredményeként fejeződik be. Ha a rendszer lemondást kér, de egy eredmény vagy kivétel továbbra is létrejön, a tevékenységnek vagy állapotban RanToCompletionFaulted kell befejeződnie.

Azoknak az aszinkron metódusoknak, amelyek elsősorban a lemondás lehetőségét szeretnék felfedni, nem kell túlterhelést biztosítaniuk, amely nem fogad el lemondási jogkivonatot. A nem megszakítható metódusok esetében ne adjon túlterheléseket, amelyek elfogadják a lemondási jogkivonatot; ez segít jelezni a hívónak, hogy a célmetódus valóban visszavonható-e. A lemondást nem igénylő fogyasztói kód meghívhat egy metódust, amely elfogadja CancellationToken az a értéket, és argumentumértéket ad meg None . None funkcionálisan megegyezik az alapértelmezett CancellationTokenértékével.

Állapotjelentés (nem kötelező)

Az aszinkron műveletek némelyike kihasználja az előrehaladási értesítéseket; ezek általában a felhasználói felület frissítésére szolgálnak az aszinkron művelet előrehaladásával kapcsolatos információkkal.

A TAP-ban a folyamat egy IProgress<T> interfészen keresztül történik, amelyet a rendszer az aszinkron metódusnak ad át egy általában elnevezett progressparaméterként. Ha az aszinkron metódus meghívásakor megadja a folyamatkezelő felületet, azzal kiküszöbölheti a helytelen használatból eredő versenyfeltételeket (vagyis ha a művelet indítása után helytelenül regisztrált eseménykezelők esetleg kihagyják a frissítéseket). Ennél is fontosabb, hogy a folyamatjelző felület támogatja a folyamat különböző implementációit, amelyeket a fogyasztó kód határoz meg. Előfordulhat például, hogy a felhasználó kód csak a legújabb állapotfrissítéssel foglalkozik, vagy szeretné pufferelni az összes frissítést, vagy minden frissítéshez meg szeretne hívni egy műveletet, vagy szeretné szabályozni, hogy a hívás egy adott szálra van-e rendezve. Mindezek a lehetőségek az interfész egy másik, az adott fogyasztó igényeinek megfelelően testre szabott implementációjával érhetők el. A lemondáshoz hasonlóan a TAP-implementációknak is csak akkor kell paramétert IProgress<T> megadniuk, ha az API támogatja az előrehaladási értesítéseket.

Ha például a ReadAsync jelen cikkben korábban tárgyalt módszer képes az eddig beolvasott bájtok számának formájában jelenteni a köztes állapotot, a folyamat visszahívása lehet egy IProgress<T> interfész:

public Task ReadAsync(byte[] buffer, int offset, int count,
                      IProgress<long> progress)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
                          count As Integer,
                          progress As IProgress(Of Long)) As Task

Ha egy FindFilesAsync metódus egy adott keresési mintának megfelelő összes fájl listáját adja vissza, az előrehaladási visszahívás becslést adhat a befejezett munka százalékos arányáról és a részleges eredmények aktuális készletéről. Ezt az információt a következők egyikével is megadhatja:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
            string pattern,
            IProgress<Tuple<double,
            ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(pattern As String,
                               progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _
                               As Task(Of ReadOnlyCollection(Of FileInfo))

vagy az API-ra jellemző adattípussal:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
    string pattern,
    IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(pattern As String,
                               progress As IProgress(Of FindFilesProgressInfo)) _
                               As Task(Of ReadOnlyCollection(Of FileInfo))

Az utóbbi esetben a speciális adattípust általában utótaggal ProgressInfoírjuk.

Ha a TAP-implementációk olyan túlterheléseket biztosítanak, amelyek elfogadják a paramétert progress , engedélyezniük kell az argumentumot null, ebben az esetben nem történik előrehaladás. A TAP-implementációknak szinkron módon kell jelentenie az előrehaladást az Progress<T> objektumnak, ami lehetővé teszi, hogy az aszinkron metódus gyorsan biztosítsa az előrehaladást. Lehetővé teszi továbbá, hogy a folyamat fogyasztója határozza meg, hogyan és hol érdemes a legjobban kezelni az információkat. A folyamatpéldány például dönthet úgy, hogy végrehajtja a visszahívásokat, és eseményeket hoz létre egy rögzített szinkronizálási környezetben.

IProgress<T-implementációk>

A .NET biztosítja az Progress<T> osztályt, amely implementálja a .NET-et IProgress<T>. Az Progress<T> osztály a következőképpen van deklarálva:

public class Progress<T> : IProgress<T>  
{  
    public Progress();  
    public Progress(Action<T> handler);  
    protected virtual void OnReport(T value);  
    public event EventHandler<T>? ProgressChanged;  
}  

Egy eseményt közzétevő példány Progress<T> , amely minden alkalommal megjelenik ProgressChanged , amikor az aszinkron művelet állapotfrissítést jelent. Az ProgressChanged esemény azon az SynchronizationContext objektumon lesz előállítva, amelyet a Progress<T> példány példányosításakor rögzítettek. Ha nem volt elérhető szinkronizálási környezet, a rendszer a szálkészletet célként szolgáló alapértelmezett környezetet használja. Előfordulhat, hogy a kezelők regisztrálva lesznek ezzel az eseménysel. A konstruktor számára egyetlen kezelő is biztosítható Progress<T> a kényelem érdekében, és ugyanúgy viselkedik, mint az ProgressChanged esemény eseménykezelője. A folyamatfrissítések aszinkron módon jelennek meg, így elkerülhető az aszinkron művelet késleltetése az eseménykezelők végrehajtása közben. Egy másik IProgress<T> implementáció dönthet úgy, hogy különböző szemantikát alkalmaz.

A biztosítandó túlterhelések kiválasztása

Ha a TAP-implementáció az opcionális CancellationToken és az opcionális IProgress<T> paramétereket is használja, az akár négy túlterhelést is igényelhet:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, CancellationToken cancellationToken);  
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
    CancellationToken cancellationToken, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task  
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
                       progress As IProgress(Of T)) As Task  

Számos TAP-implementáció azonban nem biztosít lemondási vagy előrehaladási képességeket, ezért egyetlen módszert igényelnek:

public Task MethodNameAsync(…);  
Public MethodNameAsync(…) As Task  

Ha a TAP-implementáció támogatja a lemondást vagy az előrehaladást, de mindkettőt nem, két túlterhelést okozhat:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, CancellationToken cancellationToken);  
  
// … or …  
  
public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task  
  
' … or …  
  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task  

Ha a TAP-implementáció támogatja a lemondást és az előrehaladást is, az mind a négy túlterhelést elérhetővé teheti. Ez azonban csak a következő kettőt nyújthatja:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…,
    CancellationToken cancellationToken, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken,
                       progress As IProgress(Of T)) As Task  

A két hiányzó köztes kombináció kompenzálása érdekében a fejlesztők átengedhetik None a paraméter és null a cancellationTokenprogress paraméter alapértelmezett CancellationToken értékét.

Ha a TAP metódus minden használata támogatja a lemondást vagy az előrehaladást, kihagyhatja azokat a túlterheléseket, amelyek nem fogadják el a megfelelő paramétert.

Ha úgy dönt, hogy több túlterhelést tesz elérhetővé a lemondás vagy a haladás opcionálissá tétele érdekében, a lemondást vagy előrehaladást nem támogató túlterheléseknek úgy kell viselkedniük, mintha a lemondás null vagy a túlterhelés felé haladnánakNone, amely támogatja ezeket.

Cím Leírás
Aszinkron programozási minták Bemutatja az aszinkron műveletek végrehajtásának három mintáját: a tevékenységalapú aszinkron mintát (TAP), az aszinkron programozási modellt (APM) és az eseményalapú aszinkron mintát (EAP).
A feladatalapú aszinkron minta implementálása A feladatalapú aszinkron minta (TAP) implementálását háromféleképpen ismerteti: 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.
A tevékenységalapú aszinkron minta felhasználása Ismerteti, hogyan használhatja a feladatokat és a visszahívásokat a várakozás blokkolás nélküli elérésére.
Interop with Other Asynchronous Patterns and Types Ismerteti, hogyan használható a feladatalapú aszinkron minta (TAP) az aszinkron programozási modell (APM) és az eseményalapú aszinkron minta (EAP) implementálásához.