Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans .NET, le modèle asynchrone basé sur les tâches est le modèle de conception asynchrone recommandé pour le nouveau développement. Elle est basée sur les types Task et Task<TResult> dans l’espace de noms System.Threading.Tasks, qui représentent 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. Cette approche contraste avec le modèle de programmation asynchrone (APM ou IAsyncResult) et le modèle asynchrone basé sur les événements (EAP). APM requiert les méthodes Begin et End. EAP nécessite une méthode qui possède le suffixe Async et nécessite également un ou plusieurs événements, des types délégués pour les gestionnaires d’événements, ainsi que des types dérivés de EventArg. 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 renvoie pas de type awaitable, son nom doit commencer par Begin, Start ou un autre verbe pour indiquer que cette méthode ne renvoie pas ni ne lance 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 qu'un paramètre out ou ref retourne devraient plutôt faire partie du TResult retourné par Task<TResult>, et devraient utiliser un tuple ou une structure de données personnalisée pour prendre en charge plusieurs valeurs. 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.
Comportement asynchrone, types de retour et nommage
Le async mot clé ne force pas l’exécution asynchrone d’une méthode sur un autre thread. Elle active await, et la méthode s’exécute de manière synchrone jusqu’à ce qu’elle atteigne un objet en attente incomplet. Si la méthode n’atteint pas un objet en attente incomplet, elle peut se terminer de manière synchrone.
Pour la plupart des API, préférez ces types de retour :
- Utiliser Task pour les opérations asynchrones qui ne produisent pas de valeur.
- Utiliser Task<TResult> pour les opérations asynchrones qui produisent une valeur.
- Utilisez ValueTask ou ValueTask<TResult> uniquement lorsque les mesures affichent la pression d’allocation et quand les consommateurs peuvent gérer les contraintes d’utilisation supplémentaires.
Conservez la désignation TAP cohérente :
- Utilisez le
Asyncsuffixe pour les méthodes qui retournent des types attendus. - N'ajoutez pas
Asyncaux méthodes synchrones. - Ajoutez la nouvelle surcharge
MethodNameAsyncen parallèle avec la méthode existante `MethodName`. Ne supprimez pas ou renommez l’API synchrone. La conservation des deux permet aux utilisateurs de migrer à leur propre rythme sans rupture.
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. Conservez le travail synchrone au minimum afin que la méthode asynchrone puisse retourner rapidement. Les raisons d’un retour rapide sont les suivantes :
- Vous pouvez appeler des méthodes asynchrones à partir de threads d’interface utilisateur et tout travail synchrone de longue durée peut nuire à la réactivité de l’application.
- Vous pouvez lancer plusieurs méthodes asynchrones 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 façon synchrone et retourner une tâche déjà terminée.
Exceptions
Une méthode asynchrone doit lever une exception directement à partir 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, affectez des exceptions qui se produisent lorsqu’une méthode asynchrone est en cours d’exécution à 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ée à 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 un Task qui représente l’occurrence d’une condition ailleurs dans le système (par exemple, une tâche qui représente des données arrivant dans 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, via des méthodes sur la classe Task (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 dans 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 particuliers de types qui dérivent de Task et de Task<TResult>, et pour prendre en charge la séparation de la construction de la planification, la classe Task expose une méthode Start. 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 sont déjà lancées et que leur état de tâche est une valeur d’énumération autre que TaskStatus.Created. Toutes les tâches qui sont 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 devant être retournée, elle doit appeler Start sur 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 devraient pas tenter d'appeler Start sur toute Task retournée par une méthode TAP. L'appel de la méthode Start sur une tâche active entraîne la levée d'une exception InvalidOperationException.
Pour obtenir des conseils sur les préoccupations de durée de vie et de propriété en mode "fire-and-forget" après l'activation de la tâche, consultez Conservation des méthodes asynchrones actives.
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 static 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 en cas de 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 Faulted et RanToCompletion. 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 d'une tâche via des méthodes telles que Wait et WaitAll continue à s’exécuter malgré une exception.
Si un jeton d’annulation demande l’annulation avant la méthode TAP qui accepte ce jeton est appelé, 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 produit, la tâche doit se terminer dans l'état RanToCompletion ou Faulted.
Pour les méthodes asynchrones qui veulent avant tout permettre l'annulation, vous n'avez pas besoin de fournir une surcharge n'acceptant 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 CancellationToken et fournir None comme 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. En règle générale, utilisez ces notifications pour mettre à jour une interface utilisateur avec des informations sur la progression de l’opération asynchrone.
Dans TAP, gérez la progression via une IProgress<T> interface. Transmettez cette interface à la méthode asynchrone en tant que paramètre, généralement nommé progress. Lorsque vous fournissez l’interface de progression au moment de l’appel de la méthode asynchrone, vous pouvez éliminer les conditions de concurrence résultant d’une utilisation incorrecte. Ces conditions de concurrence se produisent lorsque les gestionnaires d’événements sont incorrectement inscrits après le démarrage de l’opération et manquent des mises à jour. Plus important encore, l'interface de progression prend en charge diverses implémentations de progression, comme indiqué par le code de consommation. Par exemple, le code consommateur peut ne se préoccuper que de la dernière mise à jour de progression, ou il peut vouloir mettre en mémoire tampon toutes les mises à jour, appeler une action pour chaque mise à jour, ou contrôler si l’appel est conduit vers un thread particulier. Toutes ces options sont réalisables à l’aide d’implémentations différentes de l’interface, personnalisées 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 peut 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 static 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 pourrait fournir ces informations soit avec un tuple :
public static 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 static 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 acceptant le paramètre progress, elles devraient permettre à l'argument d'être null. Si vous passez null, 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 bénéficiaire du progrès de déterminer comment et où il est préférable de traiter les informations. Par exemple, l'instance de progression peut choisir de marshaler les rappels et de déclencher des événements dans 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 de Progress<T> expose un événement ProgressChanged, qui est déclenché chaque fois que l'opération asynchrone signale une mise à jour de la progression. L’événement ProgressChanged est déclenché sur l’objet SynchronizationContext capturé par l’instance Progress<T> lorsqu’elle est instanciée. Si aucun contexte de synchronisation n’est disponible, un contexte par défaut qui cible le pool de threads est utilisé. Vous pouvez enregistrer des gestionnaires pour cet événement. Pour plus de commodité, vous pouvez également fournir un seul gestionnaire au Progress<T> constructeur. Ce gestionnaire 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.
Choisir les 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 soit l’annulation, soit la progression, mais pas les deux, elle 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 l’annulation et la progression, elle peut exposer les quatre surcharges. Toutefois, il peut fournir uniquement 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 None ou bien une valeur par défaut CancellationToken pour le paramètre cancellationToken et null pour le paramètre progress.
Si vous attendez que chaque utilisation de la méthode TAP prenne 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 prend en charge ces paramètres.
Articles connexes
- Modèles de programmation asynchrones : introduit 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).
- Implement le modèle asynchrone basé sur les tâches : explique comment implémenter 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.
- Consommation du modèle asynchrone basé sur les tâches : explique comment utiliser des tâches et des callbacks pour attendre sans blocage.
- Interopérabilité avec d’autres modèles et types asynchrones : décrit comment utiliser TAP pour implémenter le modèle de programmation asynchrone (APM) et le modèle asynchrone basé sur les événements (EAP).