Programmation asynchroneAsynchronous programming

Si vous avez des besoins liés aux e/s (par exemple, la demande de données à un réseau, l’accès à une base de données ou la lecture et l’écriture dans un système de fichiers), vous pouvez utiliser la programmation asynchrone.If you have any I/O-bound needs (such as requesting data from a network, accessing a database, or reading and writing to a file system), you'll want to utilize asynchronous programming. L’écriture de code asynchrone est également indiquée si votre code utilise le processeur de manière intensive, notamment pour effectuer un calcul complexe.You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code.

C# possède un modèle de programmation asynchrone au niveau du langage, qui permet d’écrire facilement du code asynchrone sans avoir à jongler avec les rappels ni à se conformer à une bibliothèque qui prend en charge l’asynchronie.C# has a language-level asynchronous programming model, which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library that supports asynchrony. Il suit ce que l’on appelle le modèle asynchrone basé sur des tâches (TAP).It follows what is known as the Task-based Asynchronous Pattern (TAP).

Vue d’ensemble du modèle asynchroneOverview of the asynchronous model

La programmation asynchrone est basée sur les objets Task et Task<T>, qui modélisent les opérations asynchrones.The core of async programming is the Task and Task<T> objects, which model asynchronous operations. Ces objets sont exposés à l’aide des mots clés async et await.They are supported by the async and await keywords. Dans la plupart des cas, le modèle est assez simple :The model is fairly simple in most cases:

  • Pour le code lié aux e/s, vous attendez une opération qui retourne un Task ou Task<T> à l’intérieur d’une async méthode.For I/O-bound code, you await an operation that returns a Task or Task<T> inside of an async method.
  • Pour le code lié au processeur, vous attendez une opération démarrée sur un thread d’arrière-plan avec la Task.Run méthode.For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

Le mot clé await trouve ici toute son utilité.The await keyword is where the magic happens. Il cède le contrôle à l’appelant de la méthode qui a effectué l’opération await. Au final, c’est ce qui rend une interface utilisateur réactive ou un service élastique.It yields control to the caller of the method that performed await, and it ultimately allows a UI to be responsive or a service to be elastic. Bien qu' il existe des façons d’aborder du code asynchrone autre que async et await , cet article se concentre sur les constructions de niveau de langage.While there are ways to approach async code other than async and await, this article focuses on the language-level constructs.

Exemple de liaison d’e/s : Télécharger des données à partir d’un service WebI/O-bound example: Download data from a web service

Vous devrez peut-être télécharger des données à partir d’un service Web lorsque vous appuyez sur un bouton mais que vous ne souhaitez pas bloquer le thread d’interface utilisateur.You may need to download some data from a web service when a button is pressed but don't want to block the UI thread. Il peut être accompli de la façon suivante :It can be accomplished like this:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

Le code exprime l’intention (en téléchargeant les données de façon asynchrone) sans être coincés dans l’interaction avec les Task objets.The code expresses the intent (downloading data asynchronously) without getting bogged down in interacting with Task objects.

Exemple de processeur dépendant : effectuer un calcul pour un jeuCPU-bound example: Perform a calculation for a game

Supposons que vous développez un jeu pour mobile dans lequel l’appui sur un bouton peut causer des dommages à de nombreux ennemis à l’écran.Say you're writing a mobile game where pressing a button can inflict damage on many enemies on the screen. Le calcul des dommages infligés peut nécessiter beaucoup de ressources. Si ce calcul est effectué sur le thread d’interface utilisateur, le jeu risque d’être considérablement ralenti pendant la durée du calcul.Performing the damage calculation can be expensive, and doing it on the UI thread would make the game appear to pause as the calculation is performed!

La meilleure façon de gérer cela est de démarrer un thread d’arrière-plan, qui effectue le travail à l’aide de Task.Run , et d’attendre son résultat à l’aide de await .The best way to handle this is to start a background thread, which does the work using Task.Run, and await its result using await. Cela permet à l’interface utilisateur de paraître lisse au fur et à mesure que le travail est effectué.This allows the UI to feel smooth as the work is being done.

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work. The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

Ce code exprime clairement l’objectif de l’événement Click du bouton, il ne nécessite pas de gérer un thread d’arrière-plan manuellement, et il le fait de façon non bloquante.This code clearly expresses the intent of the button's click event, it doesn't require managing a background thread manually, and it does so in a non-blocking way.

Les dessous du codeWhat happens under the covers

Il y a de nombreux éléments mobiles dans lesquels des opérations asynchrones sont concernées.There are many moving pieces where asynchronous operations are concerned. Si vous êtes curieux de savoir ce qui se passe sous les couvertures de Task et Task<T> , consultez l’article Async in-Depth pour plus d’informations.If you're curious about what's happening underneath the covers of Task and Task<T>, see the Async in-depth article for more information.

Du côté du C#, le compilateur transforme votre code en une machine à États qui effectue le suivi d’opérations telles que le traitement de l’exécution quand un await est atteint et la reprise de l’exécution lorsqu’une tâche en arrière-plan est terminée.On the C# side of things, the compiler transforms your code into a state machine that keeps track of things like yielding execution when an await is reached and resuming execution when a background job has finished.

D’un point de vue théorique, il s’agit d’une implémentation du modèle de promesses d’asynchronisme.For the theoretically-inclined, this is an implementation of the Promise Model of asynchrony.

Éléments clés à comprendreKey pieces to understand

  • Le code asynchrone peut être utilisé pour du code utilisant les E/S ou le processeur de manière intensive, mais il est utilisé de manière différente dans chaque scénario.Async code can be used for both I/O-bound and CPU-bound code, but differently for each scenario.
  • Le code asynchrone utilise les objets Task<T> et Task, qui sont des constructions servant à modéliser le travail effectué en arrière-plan.Async code uses Task<T> and Task, which are constructs used to model work being done in the background.
  • Le mot clé async définit une méthode comme asynchrone, ce qui vous permet d’utiliser le mot clé await dans le corps de la méthode.The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
  • Quand le mot clé await est utilisé, il suspend la méthode d’appel et cède le contrôle à son appelant jusqu’à ce que la tâche awaited soit terminée.When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
  • Le mot clé await peut uniquement être utilisé dans une méthode asynchrone.await can only be used inside an async method.

Reconnaître le travail lié au processeur et à l’e/sRecognize CPU-bound and I/O-bound work

Les deux premiers exemples de ce guide ont montré comment utiliser async et pour le travail lié aux await e/s et au processeur.The first two examples of this guide showed how you could use async and await for I/O-bound and CPU-bound work. C’est la clé que vous pouvez identifier lorsqu’un travail que vous devez effectuer est lié aux e/s ou à l’UC, car cela peut affecter les performances de votre code et peut potentiellement entraîner une utilisation erronée de certaines constructions.It's key that you can identify when a job you need to do is I/O-bound or CPU-bound because it can greatly affect the performance of your code and could potentially lead to misusing certain constructs.

Voici deux questions à vous poser avant d’écrire du code :Here are two questions you should ask before you write any code:

  1. Votre code doit-il « attendre » quelque chose, par exemple des données d’une base de données ?Will your code be "waiting" for something, such as data from a database?

    Si la réponse est « oui », le travail utilise les E/S de manière intensive.If your answer is "yes", then your work is I/O-bound.

  2. Votre code effectuera-t-il un calcul coûteux ?Will your code be performing an expensive computation?

    Si la réponse est « oui », le travail utilise le processeur de manière intensive.If you answered "yes", then your work is CPU-bound.

Si le travail à faire utilise les E/S de manière intensive, utilisez async et await sans Task.Run.If the work you have is I/O-bound, use async and await without Task.Run. Vous ne devez pas utiliser la bibliothèque parallèle de tâches.You should not use the Task Parallel Library. Cette raison est décrite en détail dans Async.The reason for this is outlined in Async in Depth.

Si le travail que vous avez est lié à l' UC et que vous vous souciez de la réactivité, utilisez async et await , mais générez le travail sur un autre thread avec Task.Run .If the work you have is CPU-bound and you care about responsiveness, use async and await, but spawn off the work on another thread with Task.Run. Si le travail est approprié pour la concurrence et le parallélisme, envisagez également d’utiliser la bibliothèque parallèle de tâches.If the work is appropriate for concurrency and parallelism, also consider using the Task Parallel Library.

De plus, vous devez toujours mesurer les performances d’exécution de votre code.Additionally, you should always measure the execution of your code. Par exemple, vous constaterez peut-être que le coût d’un travail utilisant le processeur de manière intensive n’est pas si élevé que cela par rapport à la surcharge des changements de contexte induits par le multithreading.For example, you may find yourself in a situation where your CPU-bound work is not costly enough compared with the overhead of context switches when multithreading. Chaque solution ayant ses compromis, choisissez le meilleur compromis pour votre scénario.Every choice has its tradeoff, and you should pick the correct tradeoff for your situation.

Plus d'exemplesMore examples

Les exemples suivants montrent diverses façons d’écrire du code asynchrone dans C#.The following examples demonstrate various ways you can write async code in C#. Ils correspondent à plusieurs scénarios différents que vous êtes susceptible de rencontrer.They cover a few different scenarios you may come across.

Extraire des données à partir d’un réseauExtract data from a network

Cet extrait de code télécharge le code HTML à partir de la page d’accueil de https://dotnetfoundation.org et compte le nombre de fois que la chaîne « .net » se produit dans le code html.This snippet downloads the HTML from the homepage at https://dotnetfoundation.org and counts the number of times the string ".NET" occurs in the HTML. Elle utilise ASP.NET pour définir une méthode de contrôleur d’API Web qui effectue cette tâche et retourne le nombre.It uses ASP.NET to define a Web API controller method, which performs this task and returns the number.

Notes

Si vous prévoyez d’effectuer une analyse HTML dans le code de production, n’utilisez pas d’expressions régulières.If you plan on doing HTML parsing in production code, don't use regular expressions. Utilisez plutôt une bibliothèque d’analyse.Use a parsing library instead.

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
    // Suspends GetDotNetCount() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");

    return Regex.Matches(html, @"\.NET").Count;
}

Voici le même scénario écrit pour une application Windows universelle, qui effectue la même tâche quand l’utilisateur appuie sur un bouton :Here's the same scenario written for a Universal Windows App, which performs the same task when a Button is pressed:

private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
    // This is what allows the app to be responsive and not block the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, @"\.NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

Attendre la fin de plusieurs tâchesWait for multiple tasks to complete

Vous pouvez avoir un scénario qui nécessite de récupérer plusieurs éléments de données simultanément.You may find yourself in a situation where you need to retrieve multiple pieces of data concurrently. L' Task API contient deux méthodes, Task.WhenAll et Task.WhenAny , qui vous permettent d’écrire du code asynchrone qui exécute une attente non bloquante sur plusieurs travaux en arrière-plan.The Task API contains two methods, Task.WhenAll and Task.WhenAny, that allow you to write asynchronous code that performs a non-blocking wait on multiple background jobs.

Cet exemple vous montre comment récupérer des données User pour plusieurs userId.This example shows how you might grab User data for a set of userIds.

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();
    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }

    return await Task.WhenAll(getUserTasks);
}

Voici une autre façon de l’écrire de façon plus succincte, à l’aide de LINQ :Here's another way to write this more succinctly, using LINQ:

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

Bien qu’il s’agisse d’un code moins important, soyez prudent lors de la combinaison de LINQ et du code asynchrone.Although it's less code, use caution when mixing LINQ with asynchronous code. Du fait que LINQ utilise l’exécution différée, les appels asynchrones ne sont pas effectués immédiatement comme c’est le cas dans une boucle foreach, sauf si vous forcez l’itération de la séquence générée avec un appel à .ToList() ou .ToArray().Because LINQ uses deferred (lazy) execution, async calls won't happen immediately as they do in a foreach loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().

Informations et conseils importantsImportant info and advice

Avec la programmation asynchrone, il existe des détails à garder à l’esprit qui peuvent empêcher un comportement inattendu.With async programming, there are some details to keep in mind which can prevent unexpected behavior.

  • async Les méthodes doivent contenir un mot clé await dans leur corps pour pouvoir être suspendues.async methods need to have an await keyword in their body or they will never yield!

    Il ne faut pas oublier ce point.This is important to keep in mind. Si await n’est pas utilisé dans le corps d’une async méthode, le compilateur C# génère un avertissement, mais le code se compile et s’exécute comme s’il s’agissait d’une méthode normale.If await is not used in the body of an async method, the C# compiler generates a warning, but the code compiles and runs as if it were a normal method. Cela est incroyablement inefficace, car l’ordinateur d’État généré par le compilateur C# pour la méthode Async n’accomplit rien.This is incredibly inefficient, as the state machine generated by the C# compiler for the async method is not accomplishing anything.

  • Vous devez ajouter « Async » comme suffixe de chaque nom de méthode Async que vous écrivez.You should add "Async" as the suffix of every async method name you write.

Il s’agit de la Convention utilisée dans .NET pour différencier plus facilement les méthodes synchrones et asynchrones.This is the convention used in .NET to more easily differentiate synchronous and asynchronous methods. Certaines méthodes qui ne sont pas explicitement appelées par votre code (telles que les gestionnaires d’événements ou les méthodes de contrôleur Web) ne s’appliquent pas nécessairement.Certain methods that aren't explicitly called by your code (such as event handlers or web controller methods) don't necessarily apply. Étant donné qu’elles ne sont pas explicitement appelées par votre code, il n’est pas aussi important d’être explicite quant à leur nom.Because they are not explicitly called by your code, being explicit about their naming isn't as important.

  • async void ne doit être utilisé que pour les gestionnaires d’événements.async void should only be used for event handlers.

L’utilisation de async void est le seul moyen de permettre le fonctionnement des gestionnaires d’événements asynchrones, car les événements n’ont pas de types de retour (et ne peuvent donc pas utiliser les objets Task et Task<T>).async void is the only way to allow asynchronous event handlers to work because events do not have return types (thus cannot make use of Task and Task<T>). Toute autre utilisation de la méthode async void ne suit pas le modèle TAP et peut être difficile à implémenter, comme expliqué ci-après :Any other use of async void does not follow the TAP model and can be challenging to use, such as:

  • Les exceptions levées dans une async void méthode ne peuvent pas être interceptées en dehors de cette méthode.Exceptions thrown in an async void method can't be caught outside of that method.

  • async void les méthodes sont difficiles à tester.async void methods are difficult to test.

  • async void les méthodes peuvent provoquer des effets secondaires incorrects si l’appelant ne s’attend pas à ce qu’ils soient asynchrones.async void methods can cause bad side effects if the caller isn't expecting them to be async.

  • Définissez le thread avec précaution si vous utilisez des expressions lambda asynchrones dans des expressions LINQ.Tread carefully when using async lambdas in LINQ expressions

Les expressions lambda dans LINQ utilisent l’exécution différée, ce qui signifie que le code peut finir à s’exécuter à un moment où vous ne l’attendiez pas.Lambda expressions in LINQ use deferred execution, meaning code could end up executing at a time when you're not expecting it to. L’introduction de tâches bloquantes dans ce code peut facilement provoquer un interblocage si le code n’est pas écrit correctement.The introduction of blocking tasks into this can easily result in a deadlock if not written correctly. De plus, l’imbrication de code asynchrone peut rendre la logique d’exécution du code plus compliquée.Additionally, the nesting of asynchronous code like this can also make it more difficult to reason about the execution of the code. Async et LINQ sont puissants, mais doivent être utilisés ensemble aussi bien que possible.Async and LINQ are powerful but should be used together as carefully and clearly as possible.

  • Écrivez du code qui attend certaines tâches de façon non bloquante.Write code that awaits Tasks in a non-blocking manner

Le fait de bloquer le thread actuel comme un moyen d’attendre qu’un Task se termine peut entraîner des interblocages et bloquer les threads de contexte et peut nécessiter une gestion des erreurs plus complexe.Blocking the current thread as a means to wait for a Task to complete can result in deadlocks and blocked context threads and can require more complex error-handling. Le tableau suivant fournit des conseils sur la façon de traiter l’attente des tâches de façon non bloquante :The following table provides guidance on how to deal with waiting for tasks in a non-blocking way:

Élément à utiliser...Use this... Au lieu de...Instead of this... Lorsque vous souhaitez effectuer cette opération...When wishing to do this...
await Task.Wait ou Task.ResultTask.Wait or Task.Result Extraire le résultat d’une tâche en arrière-planRetrieving the result of a background task
await Task.WhenAny Task.WaitAny Attendre la fin d’une tâcheWaiting for any task to complete
await Task.WhenAll Task.WaitAll Attendre la fin de toutes les tâchesWaiting for all tasks to complete
await Task.Delay Thread.Sleep Attendre pendant une périodeWaiting for a period of time
  • Envisagez d’utiliser ValueTask dans la mesure du possibleConsider using ValueTask where possible

Le retour d’un objet Task à partir de méthodes async peut introduire des goulots d’étranglement au niveau des performances dans certains chemins.Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task est un type référence. Si vous l’utilisez, vous allouez donc un objet.Task is a reference type, so using it means allocating an object. Dans les cas où une méthode déclarée avec le async modificateur retourne un résultat mis en cache ou se termine de façon synchrone, les allocations supplémentaires peuvent devenir un coût de temps significatif dans les sections de code critiques pour les performances.In cases where a method declared with the async modifier returns a cached result or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. Cela peut devenir coûteux si ces allocations se produisent dans des boucles serrées.It can become costly if those allocations occur in tight loops. Pour plus d’informations, consultez types de retour asynchrones généralisés.For more information, see generalized async return types.

  • Envisagez d’utiliser ConfigureAwait(false)Consider using ConfigureAwait(false)

Une question courante est « quand dois-je utiliser la Task.ConfigureAwait(Boolean) méthode ? ».A common question is, "when should I use the Task.ConfigureAwait(Boolean) method?". La méthode permet à une Task instance de configurer son await.The method allows for a Task instance to configure its awaiter. Il s’agit d’un élément important à prendre en compte et sa définition de manière incorrecte peut avoir une incidence sur les performances et même des blocages.This is an important consideration and setting it incorrectly could potentially have performance implications and even deadlocks. Pour plus d’informations sur ConfigureAwait , consultez le Forum aux questions ConfigureAwait.For more information on ConfigureAwait, see the ConfigureAwait FAQ.

  • Limitez l’écriture de code avec état.Write less stateful code

Ne dépendez pas de l’état des objets globaux ou de l’exécution de certaines méthodes.Don't depend on the state of global objects or the execution of certain methods. Le code doit uniquement dépendre des valeurs de retour des méthodes.Instead, depend only on the return values of methods. Pourquoi ?Why?

  • La logique du code sera plus facile à comprendre.Code will be easier to reason about.
  • Le code sera plus facile à tester.Code will be easier to test.
  • Combiner du code asynchrone et du code synchrone est beaucoup plus simple.Mixing async and synchronous code is far simpler.
  • Les concurrences critiques peuvent généralement être évitées.Race conditions can typically be avoided altogether.
  • Rendre le code dépendant des valeurs de retour facilite la coordination du code asynchrone.Depending on return values makes coordinating async code simple.
  • En prime, le code fonctionne parfaitement avec l’injection de dépendances.(Bonus) it works really well with dependency injection.

L’objectif recommandé est d’atteindre une transparence référentielle complète ou quasi-complète dans votre code.A recommended goal is to achieve complete or near-complete Referential Transparency in your code. Votre base de code sera alors prédictible, testable et facile à gérer.Doing so will result in an extremely predictable, testable, and maintainable codebase.

Autres ressourcesOther resources