Aktivitetsbaserat asynkront mönster (TAP) i .NET: Introduktion och översikt

I .NET är det aktivitetsbaserade asynkrona mönstret det rekommenderade asynkrona designmönstret för ny utveckling. Den baseras på typerna Task och Task<TResult> i System.Threading.Tasks namnområdet, som används för att representera asynkrona åtgärder.

Namngivning, parametrar och returtyper

TAP använder en enda metod för att representera initieringen och slutförandet av en asynkron åtgärd. Detta står i kontrast till både APM-mönstret (APM eller IAsyncResult) och det händelsebaserade Asynkrona mönstret (EAP). APM kräver Begin och End metoder. EAP kräver en metod som har suffixet Async och som även kräver en eller flera händelser, händelsehanterardelegattyper och EventArg-härledda typer. Asynkrona metoder i TAP innehåller suffixet Async efter åtgärdsnamnet för metoder som returnerar väntande typer, till exempel Task, Task<TResult>, ValueTaskoch ValueTask<TResult>. En asynkron Get åtgärd som returnerar en Task<String> kan till exempel namnges GetAsync. Om du lägger till en TAP-metod i en klass som redan innehåller ett EAP-metodnamn med suffixet Async använder du suffixet TaskAsync i stället. Om klassen till exempel redan har en GetAsync metod använder du namnet GetTaskAsync. Om en metod startar en asynkron åtgärd men inte returnerar en väntande typ bör namnet börja med Begin, Starteller något annat verb som tyder på att den här metoden inte returnerar eller genererar resultatet av åtgärden.  

En TAP-metod returnerar antingen en System.Threading.Tasks.Task eller en System.Threading.Tasks.Task<TResult>, baserat på om motsvarande synkrona metod returnerar void eller en typ TResult.

Parametrarna för en TAP-metod ska matcha parametrarna för dess synkrona motsvarighet och bör anges i samma ordning. Parametrarna är ref dock out undantagna från den här regeln och bör undvikas helt. Alla data som skulle ha returnerats via en out eller ref -parametern bör i stället returneras som en del av returnerade TResult av Task<TResult>, och bör använda en tuppeln eller en anpassad datastruktur för att hantera flera värden. Överväg också att lägga till en CancellationToken parameter även om TAP-metodens synkrona motsvarighet inte erbjuder en.

Metoder som uteslutande ägnas åt att skapa, manipulera eller kombinera uppgifter (där metodens asynkrona avsikt är tydlig i metodnamnet eller i namnet på den typ som metoden tillhör) behöver inte följa det här namngivningsmönstret. sådana metoder kallas ofta för kombinatorer. Exempel på kombinatorer är WhenAll och WhenAnyoch beskrivs i avsnittet Använda inbyggda aktivitetsbaserade combinatorer i artikeln Använda det aktivitetsbaserade asynkrona mönstret.

Exempel på hur TAP-syntaxen skiljer sig från syntaxen som används i äldre asynkrona programmeringsmönster som Asynchronous Programming Model (APM) och det händelsebaserade Asynkrona mönstret (EAP) finns i Asynkrona programmeringsmönster.

Initiera en asynkron åtgärd

En asynkron metod som baseras på TAP kan utföra en liten mängd arbete synkront, till exempel verifiera argument och initiera den asynkrona åtgärden, innan den returnerar den resulterande aktiviteten. Synkront arbete bör hållas till det lägsta värdet så att den asynkrona metoden kan returneras snabbt. Orsaker till en snabbretur är:

  • Asynkrona metoder kan anropas från användargränssnittstrådar (UI) och allt långvarigt synkront arbete kan skada programmets svarstider.

  • Flera asynkrona metoder kan startas samtidigt. Därför kan allt långvarigt arbete i den synkrona delen av en asynkron metod fördröja initieringen av andra asynkrona åtgärder, vilket minskar fördelarna med samtidighet.

I vissa fall är mängden arbete som krävs för att slutföra åtgärden mindre än mängden arbete som krävs för att starta åtgärden asynkront. Att läsa från en ström där läsåtgärden kan uppfyllas av data som redan är buffrade i minnet är ett exempel på ett sådant scenario. I sådana fall kan åtgärden slutföras synkront och returnera en uppgift som redan har slutförts.

Undantag

En asynkron metod bör generera ett undantag som bara genereras från det asynkrona metodanropet som svar på ett användningsfel. Användningsfel bör aldrig inträffa i produktionskoden. Om du till exempel skickar en null-referens (Nothing i Visual Basic) som ett av metodens argument orsakar ett feltillstånd (representeras vanligtvis av ett ArgumentNullException undantag), kan du ändra den anropande koden för att säkerställa att en null-referens aldrig skickas. För alla andra fel bör undantag som inträffar när en asynkron metod körs tilldelas till den returnerade aktiviteten, även om den asynkrona metoden utförs synkront innan aktiviteten returneras. Vanligtvis innehåller en aktivitet högst ett undantag. Men om aktiviteten representerar flera åtgärder (till exempel WhenAll), kan flera undantag associeras med en enda aktivitet.

Målmiljö

När du implementerar en TAP-metod kan du avgöra var asynkron körning sker. Du kan välja att köra arbetsbelastningen i trådpoolen, implementera den med hjälp av asynkron I/O (utan att vara bunden till en tråd under större delen av åtgärdens körning), köra den på en specifik tråd (till exempel användargränssnittstråden) eller använda valfritt antal potentiella kontexter. En TAP-metod kanske inte ens har något att köra och kan bara returnera en Task som representerar förekomsten av ett villkor någon annanstans i systemet (till exempel en uppgift som representerar data som anländer till en köad datastruktur).

Anroparen för TAP-metoden kan blockera väntan på att TAP-metoden ska slutföras genom att synkront vänta på den resulterande uppgiften, eller köra ytterligare kod (fortsättning) när den asynkrona åtgärden slutförs. Skaparen av fortsättningskoden har kontroll över var koden körs. Du kan skapa fortsättningskoden antingen explicit, via metoder i Task klassen (till exempel ContinueWith) eller implicit genom att använda språkstöd som bygger på fortsättningar (till exempel await i C#, Await i Visual Basic, AwaitValue i F#).

Uppgiftsstatus

Klassen Task tillhandahåller en livscykel för asynkrona åtgärder och den cykeln representeras av TaskStatus uppräkningen. För att stödja hörnfall av typer som härleds från Task och Task<TResult>, och för att stödja separation av konstruktion från schemaläggning, Task exponerar klassen en Start metod. Uppgifter som skapas av de offentliga Task konstruktorerna kallas kalla aktiviteter eftersom de påbörjar sin livscykel i icke-schemalagt Created tillstånd och endast schemaläggs när Start anropas på dessa instanser.

Alla andra uppgifter börjar sin livscykel i ett hett tillstånd, vilket innebär att de asynkrona åtgärder som de representerar redan har initierats och deras aktivitetsstatus är ett annat uppräkningsvärde än TaskStatus.Created. Alla uppgifter som returneras från TAP-metoder måste aktiveras. Om en TAP-metod internt använder en aktivitets konstruktor för att instansiera uppgiften som ska returneras, måste TAP-metoden anropa StartTask objektet innan det returneras. Konsumenter av en TAP-metod kan på ett säkert sätt anta att den returnerade uppgiften är aktiv och inte bör försöka anropa Start någon Task som returneras från en TAP-metod. Att anropa Start en aktiv aktivitet resulterar i ett InvalidOperationException undantag.

Annullering (valfritt)

I TAP är annullering valfritt för både asynkrona metodverktyg och asynkrona metodanvändare. Om en åtgärd tillåter annullering exponeras en överlagring av den asynkrona metoden som accepterar en annulleringstoken (CancellationToken instans). Enligt konventionen heter cancellationTokenparametern .

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

Den asynkrona åtgärden övervakar denna token för annulleringsbegäranden. Om den får en begäran om annullering kan den välja att uppfylla begäran och avbryta åtgärden. Om annulleringsbegäran resulterar i att arbetet avslutas i förtid returnerar TAP-metoden en aktivitet som slutar i Canceled tillståndet. Det finns inget tillgängligt resultat och inget undantag utlöses. Tillståndet Canceled anses vara ett slutligt (slutfört) tillstånd för en uppgift, tillsammans med tillstånden Faulted och RanToCompletion . Om en aktivitet är i tillståndet Canceled returnerar truedess IsCompleted egenskap därför . När en aktivitet slutförs i Canceled tillståndet schemaläggs eller körs eventuella fortsättningar som registrerats med aktiviteten, såvida inte ett fortsättningsalternativ som NotOnCanceled angavs för att avregistrera sig från fortsättningen. All kod som asynkront väntar på en avbruten uppgift med hjälp av språkfunktioner fortsätter att köras men får ett OperationCanceledException eller ett undantag som härletts från den. Kod som blockeras synkront väntar på uppgiften via metoder som Wait och WaitAll även fortsätter att köras med ett undantag.

Om en annulleringstoken har begärt annullering före TAP-metoden som accepterar att token anropas, bör TAP-metoden returnera en Canceled uppgift. Men om annullering begärs medan den asynkrona åtgärden körs, behöver den asynkrona åtgärden inte acceptera annulleringsbegäran. Den returnerade aktiviteten bör endast sluta i Canceled tillståndet om åtgärden avslutas till följd av annulleringsbegäran. Om annullering begärs men ett resultat eller ett undantag fortfarande genereras bör aktiviteten sluta i RanToCompletion tillståndet eller Faulted .

För asynkrona metoder som vill exponera möjligheten att avbrytas först och främst behöver du inte ange en överlagring som inte accepterar en annulleringstoken. För metoder som inte kan avbrytas ska du inte ange överlagringar som accepterar en annulleringstoken. Detta hjälper till att ange för anroparen om målmetoden faktiskt kan avbrytas. Konsumentkod som inte vill avbrytas kan anropa en metod som accepterar en CancellationToken och ange None som argumentvärde. None är funktionellt likvärdigt med standardvärdet CancellationToken.

Förloppsrapportering (valfritt)

Vissa asynkrona åtgärder har nytta av att tillhandahålla förloppsmeddelanden. Dessa används vanligtvis för att uppdatera ett användargränssnitt med information om förloppet för den asynkrona åtgärden.

I TAP hanteras förloppet via ett IProgress<T> gränssnitt, som skickas till den asynkrona metoden som en parameter som vanligtvis heter progress. Att tillhandahålla förloppsgränssnittet när den asynkrona metoden anropas hjälper till att eliminera konkurrensförhållanden som uppstår till följd av felaktig användning (dvs. när händelsehanterare som är felaktigt registrerade efter att åtgärden startar kan missa uppdateringar). Ännu viktigare är att förloppsgränssnittet stöder olika implementeringar av förloppet, vilket bestäms av den förbrukande koden. Den förbrukande koden kanske till exempel bara bryr sig om den senaste förloppsuppdateringen, eller vill buffring av alla uppdateringar, eller så kanske du vill anropa en åtgärd för varje uppdatering, eller så kanske du vill styra om anropet är kopplad till en viss tråd. Alla dessa alternativ kan uppnås genom att använda en annan implementering av gränssnittet, anpassat efter den specifika konsumentens behov. Precis som vid annullering bör TAP-implementeringar endast tillhandahålla en IProgress<T> parameter om API:et stöder förloppsmeddelanden.

Om metoden ReadAsync som beskrivs tidigare i den här artikeln till exempel kan rapportera mellanliggande förlopp i form av antalet byte som lästs hittills kan återanropet för förlopp vara ett IProgress<T> gränssnitt:

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

Om en FindFilesAsync metod returnerar en lista över alla filer som uppfyller ett visst sökmönster kan förloppsåteranropet ge en uppskattning av procentandelen arbete som slutförts och den aktuella uppsättningen partiella resultat. Den kan ge den här informationen antingen en tuppeln:

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))

eller med en datatyp som är specifik för API:et:

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))

I det senare fallet är den särskilda datatypen vanligtvis suffix med ProgressInfo.

Om TAP-implementeringar ger överlagringar som accepterar en progress parameter måste de tillåta att argumentet är null, i vilket fall inga förlopp rapporteras. TAP-implementeringar bör rapportera förloppet till Progress<T> objektet synkront, vilket gör att den asynkrona metoden snabbt ger förlopp. Det gör också att konsumenten av förloppet kan avgöra hur och var bäst att hantera informationen. Till exempel kan förloppsinstansen välja att konvertera återanrop och skapa händelser i en insamlad synkroniseringskontext.

IProgress<T-implementeringar>

.NET tillhandahåller Progress<T> klassen som implementerar IProgress<T>. Klassen Progress<T> deklareras på följande sätt:

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

En instans av Progress<T> exponerar en ProgressChanged händelse som utlöses varje gång den asynkrona åtgärden rapporterar en förloppsuppdatering. Händelsen ProgressChanged aktiveras på objektet SynchronizationContext som registrerades när instansen Progress<T> instansierades. Om ingen synkroniseringskontext var tillgänglig används en standardkontext som riktar sig mot trådpoolen. Hanterare kan registreras med den här händelsen. En enskild hanterare kan också tillhandahållas konstruktorn för enkelhetens Progress<T> skull och fungerar precis som en händelsehanterare för ProgressChanged händelsen. Förloppsuppdateringar aktiveras asynkront för att undvika att fördröja den asynkrona åtgärden medan händelsehanterare körs. En annan IProgress<T> implementering kan välja att tillämpa olika semantik.

Välja de överlagringar som ska tillhandahållas

Om en TAP-implementering använder både de valfria CancellationToken och valfria IProgress<T> parametrarna kan det kräva upp till fyra överlagringar:

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  

Många TAP-implementeringar tillhandahåller dock inte funktioner för annullering eller förlopp, så de kräver en enda metod:

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

Om en TAP-implementering stöder antingen annullering eller förlopp, men inte båda, kan det ge två överlagringar:

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  

Om en TAP-implementering stöder både annullering och förlopp kan alla fyra överlagringarna exponeras. Det får dock endast innehålla följande två:

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  

För att kompensera för de två saknade mellanliggande kombinationerna kan utvecklare skicka None eller ett standardvärde CancellationToken för parametern cancellationToken och null för parametern progress .

Om du förväntar dig att varje användning av TAP-metoden stöder annullering eller förlopp kan du utelämna de överlagringar som inte accepterar den relevanta parametern.

Om du bestämmer dig för att exponera flera överlagringar för att göra annullering eller förlopp valfria bör de överlagringar som inte stöder annullering eller förlopp fungera som om de skickades None för annullering eller null för förlopp till överbelastningen som stöder dessa.

Title Description
Asynkrona programmeringsmönster Introducerar de tre mönstren för att utföra asynkrona åtgärder: det aktivitetsbaserade Asynkrona mönstret (TAP), APM (Asynchronous Programming Model) och det händelsebaserade Asynkrona mönstret (EAP).
Implementera det aktivitetsbaserade asynkrona mönstret Beskriver hur du implementerar det aktivitetsbaserade Asynkrona mönstret (TAP) på tre sätt: genom att använda C# och Visual Basic-kompilatorerna i Visual Studio manuellt eller genom en kombination av kompilatorn och manuella metoder.
Använda det aktivitetsbaserade asynkrona mönstret Beskriver hur du kan använda uppgifter och motringningar för att uppnå väntan utan att blockera.
Interop med andra asynkrona mönster och typer Beskriver hur du använder det aktivitetsbaserade Asynkrona mönstret (TAP) för att implementera APM (ASYNCHRONOUS Programming Model) och Händelsebaserat Asynkront mönster (EAP).