Partager via


Modèle asynchrone basé sur des tâches (TAP) dans .NET : Présentation et vue d’ensemble

Dans .NET, le modèle asynchrone basé sur des tâches est le modèle de conception asynchrone recommandé pour le nouveau développement. Il est basé sur les types et Task<TResult> les Task types dans l’espace System.Threading.Tasks de noms, qui sont utilisés pour représenter des opérations asynchrones.

Noms, paramètres et types de retour

TAP utilise une méthode unique pour représenter l’initiation et l’achèvement d’une opération asynchrone. Cela contraste avec le modèle de programmation asynchrone (APM ou IAsyncResult) et le modèle asynchrone basé sur les événements (EAP). APM requiert Begin et End effectue des méthodes. EAP nécessite une méthode qui a le Async suffixe et nécessite également un ou plusieurs événements, des types délégués de gestionnaire d’événements et EventArgdes types dérivés. Les méthodes asynchrones dans TAP incluent le Async suffixe après le nom de l’opération pour les méthodes qui retournent des types attendus, tels que Task, , Task<TResult>ValueTask, et ValueTask<TResult>. Par exemple, une opération asynchrone Get qui retourne un Task<String> peut être nommée GetAsync. Si vous ajoutez une méthode TAP à une classe qui contient déjà un nom de méthode EAP avec le Async suffixe, utilisez plutôt le suffixe TaskAsync . Par exemple, si la classe a déjà une GetAsync méthode, utilisez le nom GetTaskAsync. Si une méthode démarre une opération asynchrone mais ne retourne pas de type attendu, son nom doit commencer par Begin, Startou un autre verbe pour suggérer que cette méthode ne retourne pas ou ne lève pas le résultat de l’opération.  

Une méthode TAP retourne un System.Threading.Tasks.Task ou un System.Threading.Tasks.Task<TResult>, selon que la méthode synchrone correspondante retourne void ou un type TResult.

Les paramètres d’une méthode TAP doivent correspondre aux paramètres de son équivalent synchrone et doivent être fournis dans le même ordre. Toutefois, out et les ref paramètres sont exemptés de cette règle et doivent être évités entièrement. Toutes les données qui auraient été retournées par le biais d’un ou d’un out paramètre doivent plutôt être retournées dans le TResult cadre du retourTask<TResult>, et doivent utiliser un tuple ou une structure de données personnalisée pour prendre en charge plusieurs valeurs.ref En outre, envisagez d’ajouter un CancellationToken paramètre même si l’équivalent synchrone de la méthode TAP ne l’offre pas.

Les méthodes qui sont consacrées exclusivement à la création, à la manipulation ou à la combinaison de tâches (où l’intention asynchrone de la méthode est claire dans le nom de la méthode ou dans le nom du type auquel appartient la méthode) n’ont pas besoin de suivre ce modèle de nommage ; ces méthodes sont souvent appelées combinateurs. Des exemples de combinateurs incluent WhenAll et WhenAnysont abordés dans la section Utilisation des combinateurs intégrés basés sur des tâches de l’article Consommation du modèle asynchrone basé sur les tâches.

Pour obtenir des exemples de différences entre la syntaxe TAP et la syntaxe utilisée dans les modèles de programmation asynchrone hérités tels que le modèle de programmation asynchrone (APM) et le modèle asynchrone basé sur les événements (EAP), consultez les modèles de programmation asynchrone.

Lancement d’une opération asynchrone

Une méthode asynchrone basée sur TAP peut effectuer une petite quantité de travail de manière synchrone, comme la validation des arguments et le lancement de l’opération asynchrone, avant de retourner la tâche résultante. Le travail synchrone doit être conservé au minimum afin que la méthode asynchrone puisse retourner rapidement. Les raisons d’un retour rapide sont les suivantes :

  • Les méthodes asynchrones peuvent être appelées à partir de threads d’interface utilisateur et tout travail synchrone de longue durée peut nuire à la réactivité de l’application.

  • Plusieurs méthodes asynchrones peuvent être lancées simultanément. Par conséquent, tout travail de longue durée dans la partie synchrone d’une méthode asynchrone peut retarder l’initiation d’autres opérations asynchrones, réduisant ainsi les avantages de la concurrence.

Dans certains cas, la quantité de travail requise pour terminer l’opération est inférieure à la quantité de travail requise pour lancer l’opération de manière asynchrone. La lecture à partir d’un flux où l’opération de lecture peut être satisfaite par les données déjà mises en mémoire tampon est un exemple de tel scénario. Dans ce cas, l’opération peut se terminer de manière synchrone et retourner une tâche qui a déjà été terminée.

Exceptions

Une méthode asynchrone doit déclencher une exception pour être levée hors de l’appel de méthode asynchrone uniquement en réponse à une erreur d’utilisation. Les erreurs d’utilisation ne doivent jamais se produire dans le code de production. Par exemple, si vous transmettez une référence Null (Nothing en Visual Basic) comme l’un des arguments de la méthode provoque un état d’erreur (généralement représenté par une ArgumentNullException exception), vous pouvez modifier le code appelant pour vous assurer qu’une référence Null n’est jamais passée. Pour toutes les autres erreurs, les exceptions qui se produisent lorsqu’une méthode asynchrone est en cours d’exécution doivent être affectées à la tâche retournée, même si la méthode asynchrone se termine de manière synchrone avant que la tâche ne soit retournée. En règle générale, une tâche contient au plus une exception. Toutefois, si la tâche représente plusieurs opérations (par exemple, WhenAll), plusieurs exceptions peuvent être associées à une seule tâche.

Environnement cible

Lorsque vous implémentez une méthode TAP, vous pouvez déterminer où l’exécution asynchrone se produit. Vous pouvez choisir d’exécuter la charge de travail sur le pool de threads, de l’implémenter à l’aide d’E/S asynchrones (sans être lié à un thread pour la majorité de l’exécution de l’opération), l’exécuter sur un thread spécifique (tel que le thread d’interface utilisateur) ou utiliser un certain nombre de contextes potentiels. Une méthode TAP peut même n’avoir rien à exécuter et peut simplement renvoyer une Task condition qui représente l’occurrence d’une condition ailleurs dans le système (par exemple, une tâche qui représente les données arrivant à une structure de données mise en file d’attente).

L’appelant de la méthode TAP peut bloquer l’attente de la fin de la méthode TAP en attendant de façon synchrone sur la tâche résultante, ou peut exécuter du code supplémentaire (continuation) une fois l’opération asynchrone terminée. Le créateur du code de continuation a le contrôle sur l’endroit où ce code s’exécute. Vous pouvez créer le code de continuation explicitement, par le biais de méthodes sur la Task classe (par exemple ContinueWith, ) ou implicitement, à l’aide de la prise en charge du langage basée sur les continuations (par exemple, await en C#, Await en Visual Basic, AwaitValue en F#).

État de la tâche

La Task classe fournit un cycle de vie pour les opérations asynchrones et ce cycle est représenté par l’énumération TaskStatus . Pour prendre en charge les cas d’angle de types qui dérivent Task et Task<TResult>, et pour prendre en charge la séparation de la construction de la planification, la Task classe expose une Start méthode. Les tâches créées par les constructeurs publics Task sont appelées tâches à froid, car elles commencent leur cycle de vie dans l’état non planifié Created et sont planifiées uniquement lorsqu’elles Start sont appelées sur ces instances.

Toutes les autres tâches commencent leur cycle de vie dans un état chaud, ce qui signifie que les opérations asynchrones qu’elles représentent ont déjà été lancées et que leur état de tâche est une valeur d’énumération autre que TaskStatus.Created. Toutes les tâches retournées par les méthodes TAP doivent être activées. Si une méthode TAP utilise en interne le constructeur d’une tâche pour instancier la tâche à retourner, la méthode TAP doit appeler Start l’objet Task avant de le renvoyer. Les consommateurs d’une méthode TAP peuvent supposer en toute sécurité que la tâche retournée est active et ne doivent pas essayer d’appeler Start une Task méthode TAP. L’appel Start d’une tâche active entraîne une InvalidOperationException exception.

Annulation (facultatif)

Dans TAP, l’annulation est facultative pour les implémenteurs de méthodes asynchrones et les consommateurs de méthodes asynchrones. Si une opération autorise l’annulation, elle expose une surcharge de la méthode asynchrone qui accepte un jeton d’annulation (CancellationToken instance). Par convention, le paramètre est nommé 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

L’opération asynchrone surveille ce jeton pour les demandes d’annulation. S’il reçoit une demande d’annulation, il peut choisir d’honorer cette demande et d’annuler l’opération. Si la demande d’annulation se termine prématurément, la méthode TAP retourne une tâche qui se termine par l’état Canceled ; aucun résultat n’est disponible et aucune exception n’est levée. L’état Canceled est considéré comme un état final (terminé) pour une tâche, ainsi que les états et RanToCompletion les Faulted états. Par conséquent, si une tâche est dans l’état Canceled , sa IsCompleted propriété retourne true. Lorsqu’une tâche se termine dans l’état Canceled , toutes les continuations inscrites auprès de la tâche sont planifiées ou exécutées, sauf si une option de continuation telle qu’elle NotOnCanceled a été spécifiée pour refuser la continuation. Tout code qui attend de façon asynchrone une tâche annulée via l’utilisation des fonctionnalités de langage continue à s’exécuter, mais reçoit une OperationCanceledException ou une exception dérivée de celle-ci. Le code bloqué de façon synchrone en attente sur la tâche via des méthodes telles que Wait et WaitAll continue à s’exécuter avec une exception.

Si un jeton d’annulation a demandé l’annulation avant que la méthode TAP accepte ce jeton, la méthode TAP doit retourner une Canceled tâche. Toutefois, si l’annulation est demandée pendant l’exécution de l’opération asynchrone, l’opération asynchrone n’a pas besoin d’accepter la demande d’annulation. La tâche retournée doit se terminer dans l’état Canceled uniquement si l’opération se termine à la suite de la demande d’annulation. Si l’annulation est demandée, mais qu’un résultat ou une exception est toujours généré, la tâche doit se terminer par l’état ou Faulted l’étatRanToCompletion.

Pour les méthodes asynchrones qui souhaitent exposer la possibilité d’être annulées avant tout, vous n’avez pas besoin de fournir une surcharge qui n’accepte pas de jeton d’annulation. Pour les méthodes qui ne peuvent pas être annulées, ne fournissez pas de surcharges qui acceptent un jeton d’annulation ; cela permet d’indiquer à l’appelant si la méthode cible est réellement annulable. Le code consommateur qui ne souhaite pas l’annulation peut appeler une méthode qui accepte et CancellationToken fournit None comme valeur d’argument. None est fonctionnellement équivalent à la valeur par défaut CancellationToken.

Rapport de progression (facultatif)

Certaines opérations asynchrones bénéficient de la fourniture de notifications de progression ; ils sont généralement utilisés pour mettre à jour une interface utilisateur avec des informations sur la progression de l’opération asynchrone.

Dans TAP, la progression est gérée via une IProgress<T> interface, qui est passée à la méthode asynchrone en tant que paramètre généralement nommé progress. Fournir l’interface de progression lorsque la méthode asynchrone est appelée permet d’éliminer les conditions de concurrence résultant d’une utilisation incorrecte (autrement dit, lorsque les gestionnaires d’événements qui sont correctement inscrits après le démarrage de l’opération peuvent manquer de mises à jour). Plus important encore, l’interface de progression prend en charge différentes implémentations de progression, comme déterminé par le code consommateur. Par exemple, le code consommateur peut uniquement s’occuper de la dernière mise à jour de progression, ou peut vouloir mettre en mémoire tampon toutes les mises à jour, ou appeler une action pour chaque mise à jour, ou peut vouloir contrôler si l’appel est marshalé vers un thread particulier. Toutes ces options peuvent être obtenues à l’aide d’une implémentation différente de l’interface, personnalisée aux besoins particuliers du consommateur. Comme pour l’annulation, les implémentations TAP doivent fournir un IProgress<T> paramètre uniquement si l’API prend en charge les notifications de progression.

Par exemple, si la ReadAsync méthode décrite précédemment dans cet article est en mesure de signaler la progression intermédiaire sous la forme du nombre d’octets lus jusqu’à présent, le rappel de progression peut être une IProgress<T> interface :

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

Si une FindFilesAsync méthode retourne une liste de tous les fichiers qui répondent à un modèle de recherche particulier, le rappel de progression peut fournir une estimation du pourcentage de travail terminé et de l’ensemble actuel de résultats partiels. Il peut fournir ces informations avec un 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))

ou avec un type de données spécifique à l’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))

Dans ce dernier cas, le type de données spécial est généralement suffixe avec ProgressInfo.

Si les implémentations TAP fournissent des surcharges qui acceptent un progress paramètre, elles doivent autoriser l’argument à être null, auquel cas aucune progression n’est signalée. Les implémentations TAP doivent signaler la progression à l’objet Progress<T> de façon synchrone, ce qui permet à la méthode asynchrone de fournir rapidement une progression. Il permet également au consommateur de la progression de déterminer comment et où mieux gérer les informations. Par exemple, l’instance de progression peut choisir de marshaler les rappels et de déclencher des événements sur un contexte de synchronisation capturé.

Implémentations IProgress<T>

.NET fournit la Progress<T> classe, qui implémente IProgress<T>. La Progress<T> classe est déclarée comme suit :

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

Une instance d’exposition Progress<T> d’un ProgressChanged événement, qui est déclenché chaque fois que l’opération asynchrone signale une mise à jour de progression. L’événement ProgressChanged est déclenché sur l’objet SynchronizationContext capturé lorsque l’instance Progress<T> a été instanciée. Si aucun contexte de synchronisation n’a été disponible, un contexte par défaut qui cible le pool de threads est utilisé. Les gestionnaires peuvent être inscrits auprès de cet événement. Un seul gestionnaire peut également être fourni au Progress<T> constructeur pour des raisons pratiques et se comporte comme un gestionnaire d’événements pour l’événement ProgressChanged . Les mises à jour de progression sont déclenchées de manière asynchrone pour éviter de retarder l’opération asynchrone pendant l’exécution des gestionnaires d’événements. Une autre IProgress<T> implémentation peut choisir d’appliquer une sémantique différente.

Choix des surcharges à fournir

Si une implémentation TAP utilise à la fois les paramètres facultatifs CancellationToken et facultatifs IProgress<T> , elle peut nécessiter jusqu’à quatre surcharges :

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  

Toutefois, de nombreuses implémentations TAP ne fournissent pas de fonctionnalités d’annulation ou de progression. Elles nécessitent donc une seule méthode :

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

Si une implémentation TAP prend en charge l’annulation ou la progression, mais pas les deux, il peut fournir deux surcharges :

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  

Si une implémentation TAP prend en charge à la fois l’annulation et la progression, elle peut exposer les quatre surcharges. Toutefois, il ne peut fournir que les deux éléments suivants :

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  

Pour compenser les deux combinaisons intermédiaires manquantes, les développeurs peuvent passer ou une None valeur par défaut CancellationToken pour le cancellationToken paramètre et null pour le progress paramètre.

Si vous prévoyez que chaque utilisation de la méthode TAP prend en charge l’annulation ou la progression, vous pouvez omettre les surcharges qui n’acceptent pas le paramètre approprié.

Si vous décidez d’exposer plusieurs surcharges pour effectuer une annulation ou une progression facultatives, les surcharges qui ne prennent pas en charge l’annulation ou la progression doivent se comporter comme si elles sont passées None pour l’annulation ou null pour la progression vers la surcharge qui les prend en charge.

Titre Descriptif
Modèles de programmation asynchrones Présente les trois modèles pour effectuer des opérations asynchrones : le modèle asynchrone basé sur les tâches (TAP), le modèle de programmation asynchrone (APM) et le modèle asynchrone basé sur les événements (EAP).
Implémentation du modèle asynchrone basé sur des tâches Décrit comment implémenter le modèle asynchrone basé sur les tâches (TAP) de trois façons : à l’aide des compilateurs C# et Visual Basic dans Visual Studio, manuellement ou via une combinaison du compilateur et des méthodes manuelles.
Utilisation du modèle asynchrone basé sur les tâches Décrit comment utiliser des tâches et des rappels pour attendre sans bloquer.
Interopérabilité avec d’autres modèles et types asynchrones Décrit comment utiliser le modèle asynchrone basé sur les tâches (TAP) pour implémenter le modèle de programmation asynchrone (APM) et le modèle asynchrone basé sur les événements (EAP).