Modèle de programmation asynchrone des tâches

Vous pouvez éviter des goulots d'étranglement de performance et améliorer la réactivité globale de votre application à l'aide de la programmation asynchrone. Toutefois, les techniques traditionnelles pour écrire des applications asynchrones peuvent être complexes et rendre ces applications difficiles à écrire, déboguer et mettre à jour.

C# introduit une approche simplifiée, la programmation asynchrone, qui tire parti de la prise en charge asynchrone dans le runtime .NET. Le compilateur effectue le travail difficile dont se chargeait le développeur jusqu’à maintenant. En outre, votre application conserve une structure logique qui ressemble à du code synchrone. Par conséquent, vous obtenez tous les avantages de la programmation asynchrone avec peu d'effort.

Cette rubrique fournit une vue d'ensemble sur quand et comment utiliser la programmation asynchrone, et inclut des liens vers des rubriques du support, qui contiennent des informations et des exemples.

Le comportement asynchrone améliore la réactivité.

Le comportement asynchrone est essentiel pour les activités qui sont potentiellement bloquantes, par exemple l’accès au web. L'accès à une ressource Web est parfois lent ou différé. Si cette activité est bloquée dans un processus synchrone, toute l’application doit attendre. Dans un processus asynchrone, l'application peut poursuivre une autre tâche qui ne dépend pas de la ressource Web jusqu'à ce que la tâche potentiellement bloquante soit terminée.

Le tableau suivant indique les zones classiques où la programmation asynchrone améliore la réactivité. Les API répertoriées de .NET et de Windows Runtime contiennent des méthodes qui prennent en charge la programmation asynchrone.

Domaine d'application Types .NET avec les méthodes async Types Windows Runtime avec les méthodes async
Accès Web HttpClient Windows.Web.Http.HttpClient
SyndicationClient
Utilisation de fichiers JsonSerializer
StreamReader
StreamWriter
XmlReader
XmlWriter
StorageFile
Utilisation des images MediaCapture
BitmapEncoder
BitmapDecoder
Programmation WCF Opérations synchrones et asynchrones

Le comportement asynchrone est particulièrement utile pour les applications qui accèdent au thread d'interface utilisateur, car toute activité liée à l'interface utilisateur partage généralement un thread. Si un processus est bloqué dans une application synchrone, tous les processus sont bloqués. Votre application ne répond plus et vous pouvez conclure qu'elle a rencontré une défaillance, alors qu'elle attend simplement.

Lorsque vous utilisez des méthodes asynchrones, l'application continue à répondre à l'interface utilisateur. Vous pouvez redimensionner ou réduire une fenêtre, par exemple, ou vous pouvez fermer l'application si vous ne souhaitez pas attendre qu'elle se termine.

L'approche basée sur async ajoute l'équivalent d'une transmission automatique à la liste d'options dont vous disposez pour concevoir des opérations asynchrones. En d'autres termes, vous obtenez tous les avantages de la programmation asynchrone classique mais avec beaucoup moins d'efforts du point de vue du développeur.

Les méthodes asynchrones sont faciles à écrire

Les mots clés async et await en C# sont au cœur de la programmation async. Avec ces deux mots clés, vous pouvez utiliser des ressources dans .NET Framework, .NET Core ou Windows Runtime pour créer une méthode asynchrone presque aussi facilement qu’une méthode synchrone. Les méthodes asynchrones définies avec mot clé async sont appelées méthodes asynchrones.

L'exemple suivant illustre une méthode async. Presque tous les éléments du code doivent vous sembler familiers.

Vous trouverez un exemple complet de Windows Presentation Foundation (WPF) disponible en téléchargement à partir de Programmation asynchrone avec async et await en C#.

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

Vous pouvez tirer plusieurs enseignements à partir de l’exemple précédent. Démarrez par la signature de la méthode. Elle inclut le modificateur async. Le type de retour est Task<int> (consultez la section « Types de retour » pour découvrir d’autres options). Le nom de la méthode se termine dans Async. Dans le corps de la méthode, GetStringAsync retourne un Task<string>. Cela signifie que quand vous attendez (await) la tâche, vous obtenez un string (contents). Avant d’attendre la tâche, vous pouvez effectuer tout travail qui ne repose pas sur le string de GetStringAsync.

Faites particulièrement attention à l’opérateur await. Il suspend GetUrlContentLengthAsync :

  • GetUrlContentLengthAsync ne peut pas continuer tant que getStringTask n’est pas terminé.
  • Pendant ce temps, le contrôle retourne à l’appelant de GetUrlContentLengthAsync.
  • Le contrôle reprend ici quand getStringTask est terminé.
  • L’opérateur await récupère alors le résultat string auprès de getStringTask.

L’instruction return spécifie un résultat entier. Toutes les méthodes qui attendent GetUrlContentLengthAsync récupèrent la valeur de longueur.

Si GetUrlContentLengthAsync n'a aucun travail qu'il peut effectuer entre l'appel de GetStringAsync et l'attente de son achèvement, vous pouvez simplifier votre code en appelant et attendant dans l'instruction unique suivante.

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

Les caractéristiques suivantes résument ce qui fait de l’exemple précédent une méthode asynchrone :

  • La signature de la méthode inclut un modificateur async.

  • Le nom d'une méthode async, par convention, se termine par un suffixe « Async ».

  • Le type de retour est l’un des types suivants :

    • Task<TResult> si votre méthode a une instruction return dans laquelle l'opérande est de type TResult.
    • Task si votre méthode n'a aucune instruction de retour, ou si elle a une instruction de retour sans opérande.
    • void si vous écrivez un gestionnaire d’événements async.
    • Tous les autres types ayant une méthode GetAwaiter.

    Pour plus d’informations, consultez la section Types et paramètres de retour.

  • La méthode inclut généralement au moins une expression await, qui marque le point au-delà duquel la méthode ne peut pas poursuivre son exécution tant que l’opération asynchrone attendue n’est pas terminée. Dans le même temps, la méthode est interrompue, et le contrôle retourne à l'appelant de la méthode. La section suivante de cette rubrique illustre ce qui se produit au point d'interruption.

Dans les méthodes async, vous utilisez les mots clés et les types fournis pour indiquer ce que vous souhaitez faire, et le compilateur effectue le reste, notamment le suivi de ce qui doit se produire lorsque le contrôle retourne à un point d'attente dans une méthode interrompue. Il peut être difficile de gérer des processus de routine, tels que les boucles et la gestion des exceptions, dans le code asynchrone traditionnel. Dans une méthode async, vous écrivez ces éléments comme vous l’auriez fait dans une solution synchrone, et le problème est résolu.

Pour plus d’informations sur le comportement asynchrone dans les versions antérieures de .NET Framework, consultezProgrammation asynchrone .NET Framework traditionnelle et TPL.

Ce qui se produit dans une méthode asynchrone

La chose la plus importante à comprendre en programmation asynchrone est le déplacement du flux de contrôle d'une méthode à l'autre. Le diagramme suivant présente le processus :

Trace navigation of async control flow

Les numéros du diagramme correspondent aux étapes suivantes, initiées lorsqu’une méthode appelante appelle la méthode asynchrone.

  1. Une méthode appelante appelle et attend la méthode asynchrone GetUrlContentLengthAsync.

  2. GetUrlContentLengthAsync crée une instance HttpClient et appelle la méthode asynchrone GetStringAsync pour télécharger le contenu d'un site Web comme une chaîne.

  3. Quelque chose se produit dans GetStringAsync et suspend sa progression. Elle peut être obligée d'attendre la fin d'un téléchargement sur un site Web ou de toute autre activité bloquante. Pour éviter de bloquer les ressources, GetStringAsync cède le contrôle à son appelant, GetUrlContentLengthAsync.

    GetStringAsync retourne un Task<TResult>, où TResult est une chaîne, et GetUrlContentLengthAsync assigne la tâche à la variable getStringTask. La tâche représente le processus en cours de l'appel de GetStringAsync, avec l'engagement de produire une valeur de chaîne réelle lorsque le travail est terminé.

  4. Étant donné que getStringTask n'a pas encore été attendu, GetUrlContentLengthAsync peut continuer avec un autre travail qui ne dépend pas du résultat final issu de GetStringAsync. Cette opération est représentée par un appel à la méthode synchrone DoIndependentWork.

  5. DoIndependentWork est une méthode synchrone qui effectue son travail et retourne à son appelant.

  6. GetUrlContentLengthAsync n'a plus de travail à exécuter sans un résultat de getStringTask. GetUrlContentLengthAsync veut ensuite calculer et retourner la longueur de la chaîne téléchargée, mais la méthode ne peut pas calculer cette valeur avant d'avoir la chaîne.

    Par conséquent, GetUrlContentLengthAsync utilise un opérateur await pour interrompre sa progression et pour céder le contrôle à la méthode ayant appelé GetUrlContentLengthAsync. GetUrlContentLengthAsync retourne un Task<int> à l’appelant. La tâche représente la promesse de produire un résultat entier qui est la longueur de la chaîne téléchargée.

    Notes

    Si GetStringAsync (et donc getStringTask) se termine avant que GetUrlContentLengthAsync ne l’attende, le contrôle reste dans GetUrlContentLengthAsync. Le fait de suspendre, puis de retourner à GetUrlContentLengthAsync ne sert à rien si le processus asynchrone appelé getStringTask s’est déjà effectué et si GetUrlContentLengthAsync n’a pas à attendre le résultat final.

    À l’intérieur de la méthode appelante, le modèle de traitement continue. L'appelant peut effectuer d'autres tâches qui ne dépendent pas du résultat de GetUrlContentLengthAsync avant d'attendre ce résultat, ou l'appelant peut attendre immédiatement. La méthode appelante attend GetUrlContentLengthAsync, et GetUrlContentLengthAsync attend GetStringAsync.

  7. GetStringAsync se termine et génère un résultat de chaîne. Le résultat de chaîne n'est pas retourné par l'appel de GetStringAsync de la façon à laquelle vous pourriez vous attendre. (N'oubliez pas que la méthode a déjà retourné une tâche à l'étape 3.) Au lieu de cela, le résultat chaîne est stocké dans la tâche qui représente l'achèvement de la méthode, getStringTask. L'opérateur await récupère le résultat de getStringTask. L'instruction d'assignation assigne le résultat récupéré à contents.

  8. Lorsque GetUrlContentLengthAsync a le résultat de chaîne, la méthode peut calculer la longueur de la chaîne. Puis, le travail d'GetUrlContentLengthAsync est également interrompu, et le gestionnaire d'événements en attente peut reprendre. Dans l'exemple complet à la fin de la rubrique, vous pouvez vérifier que le gestionnaire d'événements récupère et imprime la valeur du résultat de la longueur. Si vous débutez en programmation asynchrone, prenez une minute pour déterminer la différence entre le comportement synchrone et le comportement asynchrone. Une méthode synchrone retourne une fois son travail terminé (étape 5), mais une méthode asynchrone retourne une valeur de tâche lorsque son travail est interrompu (étapes 3 et 6). Lorsque la méthode async termine finalement son travail, la tâche est marquée comme terminée et le résultat, le cas échéant, est stocké dans la tâche.

Méthodes asynchrones d’API

Vous pouvez vous demander où rechercher les méthodes telles que GetStringAsync qui prennent en charge la programmation async. .NET Framework 4.5 ou version ultérieure et .NET Core contiennent de nombreux membres qui fonctionnent avec async et await. Vous pouvez les identifier par le suffixe « Async » qui est ajouté au nom de membre, et par leur type de retour Task ou Task<TResult>. Par exemple, la classe System.IO.Stream contient des méthodes telles que CopyToAsync, ReadAsync et WriteAsync en même temps que les méthodes synchrones CopyTo, Read et Write.

Windows Runtime contient également de nombreuses méthodes utilisables avec async et await dans les applications Windows. Pour plus d’informations, consultez Programmation thread et asynchrone pour le développement UWP, et Programmation asynchrone (applications Windows Store) et Démarrage rapide : appel d’API asynchrones en C# ou Visual Basic si vous utilisez des versions antérieures de Windows Runtime.

Threads

Les méthodes Async sont conçues pour être des opérations non bloquantes. Une expression await dans une méthode asynchrone ne bloque pas le thread actuel lors de l’exécution de la tâche attendue. Au lieu de cela, l'expression inscrit le reste de la méthode comme continuation et retourne le contrôle à l'appelant de la méthode async.

Les mots clés async et await n’entraînent pas la création de threads supplémentaires. Les méthodes Async ne requièrent pas de multithreading, car une méthode async ne fonctionne pas sur son propre thread. La méthode s'exécute sur le contexte de synchronisation actuel et utilise du temps sur le thread uniquement lorsqu'elle est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié au processeur vers un thread d'arrière-plan, mais un thread d'arrière-plan ne permet pas à un processus qui attend simplement les résultats de devenir disponible.

L’approche basée sur async en matière de programmation asynchrone est préférable aux approches existantes, dans presque tous les cas. En particulier, cette approche est préférable à la classe BackgroundWorker pour les opérations utilisant de nombreuses E/S, car le code est plus simple et il est inutile de vous protéger contre des conditions de concurrence. Combinée à la méthode Task.Run, la programmation asynchrone est plus appropriée que BackgroundWorker pour les opérations utilisant le processeur de manière intensive, car la programmation asynchrone sépare les détails de coordination de l’exécution de votre code à partir du travail que Task.Run transfère au pool de threads.

async et await

Si vous spécifiez qu’une méthode est une méthode async en utilisant le modificateur async, vous activez les deux fonctionnalités suivantes.

  • La méthode async marquée peut utiliser await pour indiquer des points d’interruption. L'opérateurawait indique au compilateur que la méthode asynchrone ne peut pas continuer au-delà de ce point, tant que le processus asynchrone attendu n'est pas terminé. Entre-temps, le contrôle retourne à l'appelant de la méthode async.

    L’interruption d’une méthode asynchrone dans une expression await ne constitue pas une sortie de la méthode et les blocs finally ne s’exécutent pas.

  • La méthode async marquée peut elle-même être attendue par les méthodes qui l'appellent.

La méthode asynchrone contient généralement une ou plusieurs occurrences d’un opérateur await, mais l’absence des expressions await ne provoque pas d’erreur du compilateur. Si une méthode asynchrone n’utilise pas un opérateur await pour marquer un point de sélection, elle s’exécute comme le fait une méthode synchrone, en dépit du modificateur async. Le compilateur émet un avertissement pour ces méthodes.

async et await sont des mots clés contextuels. Pour plus d'informations et pour obtenir des exemples, consultez les rubriques suivantes :

Paramètres et types de retour

Une méthode async retourne généralement un Task ou un Task<TResult>. Dans une méthode async, un opérateur await est appliqué à une tâche retournée à partir d’un appel à une autre méthode async.

Vous spécifiez Task<TResult> comme type de retour si la méthode contient une instruction return qui spécifie un opérande de type TResult.

Vous utilisez Task comme type de retour si la méthode n'a aucune instruction return ou une instruction return qui ne retourne pas d'opérande.

Vous pouvez également spécifier n’importe quel autre type de retour, sous réserve que ce type inclue une méthode GetAwaiter. ValueTask<TResult> est un exemple d’un tel type. Il est disponible dans le package NuGet System.Threading.Tasks.Extension.

L’exemple suivant montre comment déclarer et appeler une méthode qui retourne Task<TResult> ou Task :

async Task<int> GetTaskOfTResultAsync()
{
    int hours = 0;
    await Task.Delay(0);

    return hours;
}


Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()
{
    await Task.Delay(0);
    // No return statement needed
}

Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();

Chaque tâche retournée représente le travail en cours. Une tâche encapsule des informations sur l’état du processus asynchrone et, éventuellement, le résultat final du processus ou l’exception que le processus déclenche s’il ne réussit pas.

Une méthode async peut également avoir un type de retour void. Ce type de retour est essentiellement utilisé pour définir les gestionnaires d'événements, où un type de retour void est obligatoire. Les gestionnaires d'événements asynchrones servent souvent de point de départ aux programmes asynchrones.

Une méthode asynchrone dont le type de retour est void ne peut pas être attendue, et l’appelant d’une méthode retournant void ne peut capturer aucune exception levée par la méthode.

Une méthode async ne peut pas déclarer de paramètres in, ref ou out, mais elle peut appeler des méthodes qui ont ces paramètres. De même, une méthode async ne peut pas retourner une valeur par référence, bien qu’elle puisse appeler des méthodes avec des valeurs de retour de référence.

Pour plus d’informations ainsi que des exemples, consultez la page Types de retour asynchrone (C#).

Les API asynchrones dans la programmation Windows Runtime ont l’un des types de retour suivants, qui sont semblables aux tâches :

Conventions d’affectation de noms

Par convention, les méthodes qui retournent généralement des types pouvant être attendus (par exemple, Task, Task<T>, ValueTask, ValueTask<T>) doivent avoir des noms qui se terminent par « Async ». Les méthodes qui démarrent une opération asynchrone, mais qui ne retournent pas un type awaitable ne doivent pas avoir un nom qui se termine par « Async ». Cependant, leur nom peut commencer par « Begin », « Start » ou un autre mot suggérant que cette méthode ne retourne pas ou ne lève pas le résultat de l’opération.

Vous pouvez ignorer la convention où un événement, une classe de base, ou un contrat d'interface suggère un nom différent. Par exemple, vous ne devez pas renommer les gestionnaires d’événements communs, tels que OnButtonClick.

Articles connexes (Visual Studio)

Titre Description
Guide pratique pour effectuer plusieurs requêtes web en parallèle en utilisant async et await (C#) Explique comment démarrer plusieurs tâches en même temps.
Types de retour async (C#) Décrit les types que les méthodes async peuvent retourner et explique quand chaque type est approprié.
Annuler des tâches avec un jeton d’annulation comme mécanisme de signalisation. Indique comment ajouter les fonctionnalités suivantes à votre solution async :

- Annuler une liste de tâches (C#)
- Annuler les tâches après un laps de temps (C#)
- Traiter les tâches asynchrones terminées (C#)
Utiliser async pour l’accès aux fichiers (C#) Répertorie et explique les avantages de l'utilisation d'async et d'await pour accéder aux fichiers.
Modèle asynchrone basé sur les tâches (TAP) Décrit un modèle asynchrone, le modèle est basé sur les types Task et Task<TResult>.
Vidéos Async sur Channel 9 Fournit des liens vers diverses vidéos sur la programmation asynchrone.

Voir aussi