Async/Await — Programmation asynchrone
Notions théoriques
Pourquoi l'asynchronie ?
Par défaut, C# exécute les instructions de façon synchrone : chaque ligne attend que la précédente se termine. Cela pose problème pour les opérations longues (appels réseau, lecture de fichiers, requêtes SQL) : pendant ce temps, le thread est bloqué et ne peut rien faire d'autre.
La programmation asynchrone permet de lancer une opération longue et de continuer à exécuter d'autre code en attendant le résultat. En C#, cela s'appuie sur les mots-clés async et await.
En ASP.NET Core, chaque requête HTTP utilise un thread. Si ce thread est bloqué à attendre une base de données, il ne peut pas traiter d'autres requêtes. L'asynchronie permet de servir beaucoup plus d'utilisateurs simultan ément avec le même nombre de threads.
async et Task
Une méthode asynchrone est déclarée avec le mot-clé async. Elle retourne toujours un Task ou un Task<T> :
async Task: méthode qui ne retourne pas de valeur (équivalent devoid)async Task<T>: méthode qui retourne une valeur de typeT
// Méthode asynchrone sans retour
async Task SauvegarderAsync(Personnage p) { ... }
// Méthode asynchrone avec retour
async Task<List<Personnage>> ChargerTousAsync() { ... }
Le mot-clé await
await suspend l'exécution de la méthode courante jusqu'à ce que le Task soit terminé, sans bloquer le thread. La méthode reprend ensuite là où elle s'était arrêtée.
// Appel synchrone (bloque le thread - à éviter)
var personnages = service.ChargerTous();
// Appel asynchrone (libère le thread pendant l'attente)
var personnages = await service.ChargerTousAsync();
N'utilisez jamais .Result ou .Wait() pour attendre un Task. Ces appels bloquent le thread et peuvent provoquer des deadlocks dans des applications ASP.NET Core. Utilisez toujours await.
HttpClient pour les appels HTTP
HttpClient est la classe standard pour effectuer des requêtes HTTP asynchrones en C# :
using System.Net.Http;
using System.Net.Http.Json;
var client = new HttpClient();
var personnages = await client.GetFromJsonAsync<List<Personnage>>("https://api.exemple.com/personnages");
N'instanciez pas HttpClient dans une boucle : créez-en un seul par application (ou utilisez IHttpClientFactory dans ASP.NET Core).
Parallélisme avec Task.WhenAll
Pour lancer plusieurs opérations en parallèle et attendre qu'elles soient toutes terminées :
// Lance les deux requêtes en parallèle, attend que les deux soient finies
var tacheGuerriers = repo.ChargerParTypeAsync("Guerrier");
var tacheMages = repo.ChargerParTypeAsync("Mage");
await Task.WhenAll(tacheGuerriers, tacheMages);
var guerriers = tacheGuerriers.Result; // .Result est sûr APRÈS WhenAll
var mages = tacheMages.Result;
CancellationToken
Un CancellationToken permet d'annuler une opération asynchrone en cours (par exemple si l'utilisateur quitte la page avant la fin d'un chargement) :
async Task<List<Personnage>> ChargerAsync(CancellationToken ct)
{
return await _context.Personnages
.ToListAsync(ct); // s'arrête si ct est annulé
}
Dans ASP.NET Core, chaque action reçoit automatiquement un CancellationToken lié au cycle de vie de la requête HTTP.
Exemple pratique
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
// Modèle correspondant à l'API JSONPlaceholder
public class Post
{
public int Id { get; set; }
public int UserId { get; set; }
public string Title { get; set; } = "";
public string Body { get; set; } = "";
}
class Program
{
// HttpClient partagé pour toute l'application
private static readonly HttpClient _client = new HttpClient();
static async Task Main(string[] args)
{
Console.WriteLine("Chargement des articles depuis l'API...");
try
{
// Appel HTTP asynchrone
var posts = await ChargerPostsAsync();
Console.WriteLine($"{posts?.Count ?? 0} articles chargés.");
// Afficher les 3 premiers
foreach (var post in posts!.Take(3))
Console.WriteLine($" [{post.Id}] {post.Title}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Erreur réseau : {ex.Message}");
}
}
static async Task<List<Post>?> ChargerPostsAsync()
{
// GetFromJsonAsync désérialise automatiquement le JSON
return await _client.GetFromJsonAsync<List<Post>>(
"https://jsonplaceholder.typicode.com/posts"
);
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous allez créer un programme qui interroge l'API publique JSONPlaceholder pour récupérer et afficher des données. Cette API simule un backend REST sans authentification.
Étape 1 — Créer la méthode de chargement asynchrone
Créez une méthode ChargerUtilisateursAsync() qui effectue un appel GET sur https://jsonplaceholder.typicode.com/users et retourne la liste désérialisée.
public class Utilisateur
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
Par convention, toute méthode qui retourne un Task doit avoir le suffixe Async dans son nom (ex: ChargerAsync, SauvegarderAsync). Cela permet à l'appelant de savoir immédiatement qu'il doit utiliser await.
Étape 2 — Appeler la méthode depuis Main
Appelez ChargerUtilisateursAsync() depuis Main de façon asynchrone et affichez le nombre d'utilisateurs chargés.
Depuis C# 7.1, il est possible de déclarer Main avec le type de retour async Task. Cela évite d'avoir à créer une méthode intermédiaire et permet d'utiliser await directement dans le point d'entrée du programme.
Étape 3 — Charger deux ressources en parallèle
Chargez simultanément les utilisateurs et les articles (posts) de l'API, en utilisant Task.WhenAll.
Sans Task.WhenAll, les deux requêtes seraient lancées séquentiellement : si chacune prend 200 ms, le total est 400 ms. Avec Task.WhenAll, elles s'exécutent en parallèle et le total est d'environ 200 ms. Utilisez ce pattern chaque fois que plusieurs opérations sont indépendantes l'une de l'autre.