Share via


Asynchroon patroon (TAP) op basis van taken in .NET: Inleiding en overzicht

In .NET is het op taken gebaseerde asynchrone patroon het aanbevolen asynchrone ontwerppatroon voor nieuwe ontwikkeling. Deze is gebaseerd op de Task en Task<TResult> typen in de System.Threading.Tasks naamruimte, die worden gebruikt om asynchrone bewerkingen weer te geven.

Naamgeving, parameters en retourtypen

TAP gebruikt één methode om de start en voltooiing van een asynchrone bewerking aan te geven. Dit contrasteert met zowel het Asynchrone programmeermodelpatroon (APM of IAsyncResult) als het op gebeurtenissen gebaseerde Asynchrone patroon (EAP). APM vereist Begin en End methoden. EAP vereist een methode met het Async achtervoegsel en vereist ook een of meer gebeurtenissen, gebeurtenishandlerdelegentypen en EventArg-afgeleide typen. Asynchrone methoden in TAP bevatten het Async achtervoegsel na de naam van de bewerking voor methoden die te verwachten typen retourneren, zoals Task, Task<TResult>, ValueTasken ValueTask<TResult>. Een asynchrone Get bewerking die een retourneert, kan bijvoorbeeld een Task<String> naam GetAsynckrijgen. Als u een TAP-methode toevoegt aan een klasse die al een EAP-methodenaam met het Async achtervoegsel bevat, gebruikt u in plaats daarvan het achtervoegsel TaskAsync . Als de klasse bijvoorbeeld al een GetAsync methode heeft, gebruikt u de naam GetTaskAsync. Als een methode een asynchrone bewerking start, maar geen wachtbaar type retourneert, moet de naam beginnen met Begin, Startof een ander werkwoord om aan te geven dat deze methode het resultaat van de bewerking niet retourneert of genereert.  

Een TAP-methode retourneert een System.Threading.Tasks.Task of een System.Threading.Tasks.Task<TResult>, op basis van of de bijbehorende synchrone methode ongeldigheid of een type TResultretourneert.

De parameters van een TAP-methode moeten overeenkomen met de parameters van de synchrone tegenhanger en moeten in dezelfde volgorde worden opgegeven. out Parameters ref zijn echter uitgesloten van deze regel en moeten volledig worden vermeden. Alle gegevens die via een out of ref parameter zouden moeten worden geretourneerd, moeten in plaats daarvan worden geretourneerd als onderdeel van de TResult geretourneerde gegevens Task<TResult>en moeten een tuple of een aangepaste gegevensstructuur gebruiken om meerdere waarden aan te passen. Overweeg ook om een CancellationToken parameter toe te voegen, zelfs als de synchrone tegenhanger van de TAP-methode er geen biedt.

Methoden die uitsluitend zijn gewijd aan het maken, bewerken of combineren van taken (waarbij de asynchrone intentie van de methode duidelijk is in de naam van de methode of in de naam van het type waartoe de methode behoort), hoeven dit naamgevingspatroon niet te volgen; dergelijke methoden worden vaak combinaties genoemd. Voorbeelden van combinaties zijn onder andere WhenAll en WhenAny, en worden besproken in de sectie Op taken gebaseerde combinaties gebruiken van het artikel Het gebruik van het Asynchrone patroon op basis van taken.

Zie Asynchrone programmeerpatronen voor voorbeelden van hoe de TAP-syntaxis verschilt van de syntaxis die wordt gebruikt in verouderde asynchrone programmeerpatronen, zoals het Asynchrone programmeermodel (APM) en het op gebeurtenissen gebaseerde Asynchrone patroon (EAP).

Een asynchrone bewerking starten

Een asynchrone methode die is gebaseerd op TAP, kan een kleine hoeveelheid werk synchroon uitvoeren, zoals het valideren van argumenten en het initiëren van de asynchrone bewerking, voordat de resulterende taak wordt geretourneerd. Synchroon werk moet tot het minimum worden bewaard, zodat de asynchrone methode snel kan worden geretourneerd. Redenen voor een snelle terugkeer zijn onder andere:

  • Asynchrone methoden kunnen worden aangeroepen vanuit ui-threads (user interface) en elk langlopend synchroon werk kan de reactiesnelheid van de toepassing schaden.

  • Meerdere asynchrone methoden kunnen gelijktijdig worden gestart. Daarom kan elk langlopend werk in het synchrone gedeelte van een asynchrone methode de start van andere asynchrone bewerkingen vertragen, waardoor de voordelen van gelijktijdigheid afnemen.

In sommige gevallen is de hoeveelheid werk die nodig is om de bewerking te voltooien, kleiner dan de hoeveelheid werk die nodig is om de bewerking asynchroon te starten. Lezen vanuit een stroom waarbij aan de leesbewerking kan worden voldaan door gegevens die al in het geheugen zijn gebufferd, is een voorbeeld van een dergelijk scenario. In dergelijke gevallen kan de bewerking synchroon worden voltooid en kan een taak worden geretourneerd die al is voltooid.

Uitzonderingen

Een asynchrone methode moet een uitzondering genereren die alleen wordt gegenereerd uit de asynchrone methodeaanroep als reactie op een gebruiksfout. Gebruiksfouten mogen nooit optreden in productiecode. Als u bijvoorbeeld een null-verwijzing (Nothing in Visual Basic) doorgeeft als een van de argumenten van de methode een foutstatus veroorzaakt (meestal vertegenwoordigd door een ArgumentNullException uitzondering), kunt u de aanroepende code wijzigen om ervoor te zorgen dat er nooit een null-verwijzing wordt doorgegeven. Voor alle andere fouten moeten uitzonderingen die optreden wanneer een asynchrone methode wordt uitgevoerd, worden toegewezen aan de geretourneerde taak, zelfs als de asynchrone methode synchroon wordt uitgevoerd voordat de taak wordt geretourneerd. Normaal gesproken bevat een taak maximaal één uitzondering. Als de taak echter meerdere bewerkingen vertegenwoordigt (bijvoorbeeld WhenAll), kunnen er meerdere uitzonderingen aan één taak worden gekoppeld.

Doelomgeving

Wanneer u een TAP-methode implementeert, kunt u bepalen waar asynchrone uitvoering plaatsvindt. U kunt ervoor kiezen om de workload in de threadgroep uit te voeren, deze te implementeren met behulp van asynchrone I/O (zonder dat deze is gebonden aan een thread voor het merendeel van de uitvoering van de bewerking), deze uit te voeren op een specifieke thread (zoals de UI-thread) of een willekeurig aantal mogelijke contexten te gebruiken. Een TAP-methode kan zelfs niets hebben om uit te voeren en kan alleen een Task gebeurtenis retourneren die het optreden van een voorwaarde ergens anders in het systeem vertegenwoordigt (bijvoorbeeld een taak die gegevens vertegenwoordigt die in een gegevensstructuur in de wachtrij terechtkomt).

De aanroeper van de TAP-methode kan het wachten blokkeren totdat de TAP-methode is voltooid door synchroon te wachten op de resulterende taak of kan extra code (vervolgcode) uitvoeren wanneer de asynchrone bewerking is voltooid. De maker van de vervolgcode heeft controle over waar die code wordt uitgevoerd. U kunt de vervolgcode expliciet maken, via methoden in de Task klasse (bijvoorbeeld ContinueWith) of impliciet, door taalondersteuning te gebruiken die is gebouwd op basis van voortzettingen (bijvoorbeeld await in C#, Await in Visual Basic, AwaitValue in F#).

Taakstatus

De Task klasse biedt een levenscyclus voor asynchrone bewerkingen en die cyclus wordt vertegenwoordigd door de TaskStatus opsomming. Ter ondersteuning van hoekcases van typen die zijn afgeleid van Task en Task<TResult>, en ter ondersteuning van de scheiding van constructie van planning, maakt de Task klasse een Start methode beschikbaar. Taken die door de openbare Task constructors worden gemaakt, worden koude taken genoemd, omdat ze hun levenscyclus in de niet-geplande Created status beginnen en alleen worden gepland wanneer Start deze exemplaren worden aangeroepen.

Alle andere taken beginnen hun levenscyclus in een dynamische status, wat betekent dat de asynchrone bewerkingen die ze vertegenwoordigen al zijn gestart en hun taakstatus een andere opsommingswaarde is dan TaskStatus.Created. Alle taken die worden geretourneerd vanuit TAP-methoden, moeten worden geactiveerd. Als een TAP-methode intern gebruikmaakt van de constructor van een taak om de taak te instantiëren die moet worden geretourneerd, moet de TAP-methode het Task object aanroepen Start voordat het wordt geretourneerd. Consumenten van een TAP-methode kunnen er veilig van uitgaan dat de geretourneerde taak actief is en niet mag proberen aan te roepen Start op een Task taak die wordt geretourneerd vanuit een TAP-methode. Het aanroepen van Start een actieve taak resulteert in een InvalidOperationException uitzondering.

Annulering (optioneel)

In TAP is annulering optioneel voor zowel asynchrone methode-implementers als asynchrone methodegebruikers. Als een bewerking annulering toestaat, wordt er een overbelasting weergegeven van de asynchrone methode die een annuleringstoken (CancellationToken instantie) accepteert. Volgens conventie heeft de parameter de naam 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

De asynchrone bewerking bewaakt dit token op annuleringsaanvragen. Als het een annuleringsaanvraag ontvangt, kan het ervoor kiezen deze aanvraag te respecteren en de bewerking te annuleren. Als de annuleringsaanvraag resulteert in een voortijdige beëindiging van het werk, retourneert de TAP-methode een taak die eindigt op de Canceled status. Er is geen beschikbaar resultaat en er wordt geen uitzondering gegenereerd. De Canceled status wordt beschouwd als een definitieve (voltooide) status voor een taak, samen met de Faulted statussen.RanToCompletion Als een taak de status heeft Canceled , IsCompleted wordt de eigenschap daarom geretourneerd true. Wanneer een taak in de Canceled status is voltooid, worden eventuele vervolgvervolgingen die zijn geregistreerd bij de taak gepland of uitgevoerd, tenzij een vervolgoptie zoals NotOnCanceled is opgegeven om zich af te melden voor vervolg. Alle code die asynchroon wacht op een geannuleerde taak via het gebruik van taalfuncties, blijft actief, maar ontvangt een OperationCanceledException of een uitzondering die daaruit is afgeleid. Code die synchroon wordt geblokkeerd terwijl op de taak wordt gewacht via methoden zoals Wait en WaitAll die ook worden uitgevoerd met een uitzondering.

Als een annuleringstoken annulering heeft aangevraagd voordat de TAP-methode die dat token accepteert, wordt aangeroepen, moet de TAP-methode een Canceled taak retourneren. Als er echter annulering wordt aangevraagd terwijl de asynchrone bewerking wordt uitgevoerd, hoeft de asynchrone bewerking de annuleringsaanvraag niet te accepteren. De geretourneerde taak mag alleen eindigen als Canceled de bewerking eindigt als gevolg van de annuleringsaanvraag. Als annulering wordt aangevraagd, maar een resultaat of een uitzondering nog steeds wordt geproduceerd, moet de taak eindigen in de RanToCompletion of-status Faulted .

Voor asynchrone methoden die de mogelijkheid willen blootstellen om in de eerste plaats te worden geannuleerd, hoeft u geen overbelasting op te geven die geen annuleringstoken accepteert. Voor methoden die niet kunnen worden geannuleerd, biedt u geen overbelastingen die een annuleringstoken accepteren; dit helpt de aanroeper aan te geven of de doelmethode daadwerkelijk kan worden geannuleerd. Consumentencode die geen annulering wenst, kan een methode aanroepen die een CancellationToken methode accepteert en als argumentwaarde opgeeft None . None is functioneel gelijk aan de standaardwaarde CancellationToken.

Voortgangsrapportage (optioneel)

Sommige asynchrone bewerkingen profiteren van het bieden van voortgangsmeldingen; deze worden doorgaans gebruikt om een gebruikersinterface bij te werken met informatie over de voortgang van de asynchrone bewerking.

In TAP wordt de voortgang verwerkt via een IProgress<T> interface, die wordt doorgegeven aan de asynchrone methode als een parameter die meestal de naam progressheeft. Door de voortgangsinterface op te geven wanneer de asynchrone methode wordt aangeroepen, kunnen racevoorwaarden worden geëlimineerd die het gevolg zijn van onjuist gebruik (dat wil gezegd, wanneer gebeurtenis-handlers die onjuist zijn geregistreerd nadat de bewerking is gestart, updates kunnen missen). Belangrijker is dat de voortgangsinterface verschillende implementaties van de voortgang ondersteunt, zoals wordt bepaald door de verbruikende code. De verbruikende code kan bijvoorbeeld alleen betrekking hebben op de meest recente voortgangsupdate, of alle updates bufferen, of een actie voor elke update willen aanroepen, of mogelijk willen bepalen of de aanroep naar een bepaalde thread is gestaakt. Al deze opties kunnen worden bereikt door een andere implementatie van de interface te gebruiken, aangepast aan de behoeften van de specifieke consument. Net als bij annulering moeten TAP-implementaties alleen een IProgress<T> parameter opgeven als de API voortgangsmeldingen ondersteunt.

Als de ReadAsync methode die eerder in dit artikel is besproken, bijvoorbeeld tussenliggende voortgang kan rapporteren in de vorm van het aantal bytes dat tot nu toe is gelezen, kan de callback van de voortgang een IProgress<T> interface zijn:

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

Als een FindFilesAsync methode een lijst retourneert van alle bestanden die voldoen aan een bepaald zoekpatroon, kan de callback van de voortgang een schatting geven van het percentage voltooid werk en de huidige set gedeeltelijke resultaten. Het kan deze informatie verstrekken met een tuple:

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

of met een gegevenstype dat specifiek is voor de API:

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

In het laatste geval wordt het speciale gegevenstype meestal voorzien ProgressInfovan een achtervoegsel.

Als TAP-implementaties overbelastingen bieden die een progress parameter accepteren, moeten ze het argument toestaan, nullin welk geval geen voortgang wordt gerapporteerd. TAP-implementaties moeten de voortgang van het Progress<T> object synchroon rapporteren, waardoor de asynchrone methode snel voortgang kan bieden. Daarnaast kan de consument van de voortgang bepalen hoe en waar de informatie het beste kan worden afgehandeld. Het voortgangsexemplaren kan er bijvoorbeeld voor kiezen om marshal callbacks te maken en gebeurtenissen te genereren in een vastgelegde synchronisatiecontext.

IProgress T-implementaties<>

.NET biedt de Progress<T> klasse, die implementeert IProgress<T>. De Progress<T> klasse wordt als volgt gedeclareerd:

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

Een exemplaar van het beschikbaar maken van Progress<T> een ProgressChanged gebeurtenis, die telkens wordt gegenereerd wanneer de asynchrone bewerking een voortgangsupdate rapporteert. De ProgressChanged gebeurtenis wordt gegenereerd op het SynchronizationContext object dat is vastgelegd toen het Progress<T> exemplaar werd geïnstantieerd. Als er geen synchronisatiecontext beschikbaar was, wordt een standaardcontext gebruikt die is gericht op de threadgroep. Handlers kunnen worden geregistreerd bij deze gebeurtenis. Een enkele handler kan ook voor het gemak aan de Progress<T> constructor worden verstrekt en gedraagt zich net als een gebeurtenis-handler voor de ProgressChanged gebeurtenis. Voortgangsupdates worden asynchroon gegenereerd om te voorkomen dat de asynchrone bewerking wordt vertraagd terwijl gebeurtenis-handlers worden uitgevoerd. Een andere IProgress<T> implementatie kan ervoor kiezen om verschillende semantiek toe te passen.

De overbelastingen kiezen die moeten worden geboden

Als een TAP-implementatie zowel de optionele als optionele CancellationTokenIProgress<T> parameters gebruikt, kan dit maximaal vier overbelastingen vereisen:

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  

Veel TAP-implementaties bieden echter geen mogelijkheden voor annulering of voortgang, dus ze vereisen één methode:

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

Als een TAP-implementatie annulering of voortgang ondersteunt, maar niet beide, kan dit twee overbelastingen bieden:

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  

Als een TAP-implementatie zowel annulering als voortgang ondersteunt, kan deze alle vier de overbelastingen blootstellen. Het kan echter slechts de volgende twee bieden:

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  

Om te compenseren voor de twee ontbrekende tussenliggende combinaties, kunnen ontwikkelaars een standaardwaarde CancellationToken doorgeven None voor de cancellationToken parameter en null voor de progress parameter.

Als u verwacht dat elk gebruik van de TAP-methode ondersteuning biedt voor annulering of voortgang, kunt u de overbelasting weglaten die de relevante parameter niet accepteren.

Als u besluit om meerdere overbelastingen beschikbaar te maken om annulering of voortgang optioneel te maken, moeten de overbelastingen die geen ondersteuning bieden voor annulering of voortgang, zich gedragen alsof ze zijn doorgegeven None voor annulering of null voor de voortgang van de overbelasting die deze wel ondersteunt.

Title Beschrijving
Asynchrone programmeerpatronen Introduceert de drie patronen voor het uitvoeren van asynchrone bewerkingen: het op taken gebaseerde Asynchrone patroon (TAP), het Asynchrone programmeermodel (APM) en het op gebeurtenissen gebaseerde Asynchrone patroon (EAP).
Het asynchrone patroon op basis van taken implementeren Hierin wordt beschreven hoe u het op taken gebaseerde Asynchrone patroon (TAP) op drie manieren implementeert: met behulp van de C#- en Visual Basic-compilers in Visual Studio, handmatig of via een combinatie van de compiler en handmatige methoden.
Het Asynchrone patroon op basis van taken gebruiken Hierin wordt beschreven hoe u taken en callbacks kunt gebruiken om te wachten zonder te blokkeren.
Interoperabiliteit met andere asynchrone patronen en typen Beschrijft hoe u het op taken gebaseerde Asynchrone patroon (TAP) gebruikt om het Asynchrone programmeermodel (APM) en op gebeurtenissen gebaseerde Asynchrone patroon (EAP) te implementeren.