1. Introduction
Avant de plonger dans le code, clarifions la motivation et imaginons une situation typique. Supposons que votre programme télécharge un fichier depuis internet :
// Pseudocode
var data = DownloadFile("https://example.com/file");
ProcessData(data);
Le problème est le suivant : tant que le téléchargement est en cours, le programme « gèle ». Aucune autre action ne s'exécute — l'utilisateur ne peut ni bouger la souris, ni cliquer sur des boutons, ni même attendre la fin sans une interface « figée » ennuyeuse.
Avant (et dans d'autres langages), pour résoudre ce problème il fallait utiliser des threads (Thread), des tasks (Task), des délégués, des timers — tout cela menait à un empilement de code difficile à lire et à maintenir. En C#, l'équipe a décidé de simplifier la vie : l'asynchronie avec les mots-clés async et await est apparue. Maintenant on peut écrire du code asynchrone presque aussi simplement que du code normal.
La "douleur" classique de l'asynchronie sans async/await
Pour contraste, regardons comment se déroulerait une opération longue avec des threads si on voulait ne pas bloquer l'interface :
// Exemple sans async/await, manuel
var thread = new Thread(() =>
{
var data = DownloadFile("https://example.com/file");
Console.WriteLine("Fichier téléchargé !");
});
thread.Start();
Cette méthode est assez rustique : il faut gérer les threads à la main, il n'y a pas de façon simple « d'attendre » le résultat, et gérer les erreurs est compliqué.
2. Asynchronie en C#: syntaxe
Définition : qu'est-ce que async et await ?
async — c'est un modificateur qui empêche la méthode d'être ennuyeuse et la rend asynchrone. Une telle méthode retourne généralement soit un Task (ou Task<T>), soit un ValueTask. C'est une promesse que le résultat viendra, un peu plus tard (comme une commande dans une boutique en ligne — on commande et on attend).
await — c'est un opérateur qui dit : « Arrête-toi sur cette ligne quand tu y arrives. Attends que l'opération se termine, mais sans bloquer le thread ! Le reste peut continuer après. ».
À quoi ressemble une méthode asynchrone ?
public async Task MyAsyncMethod()
{
Console.WriteLine("Téléchargement du fichier...");
var data = await DownloadFileAsync("https://example.com/file"); // On attend le résultat de façon asynchrone !
Console.WriteLine("Terminé !");
}
Remarquez :
- Le modificateur async a été ajouté à la méthode.
- À l'intérieur de la méthode on utilise await pour l'opération asynchrone.
- La méthode retourne un Task (ou Task<T> si elle retourne une valeur).
Visualisation : que se passe-t-il lors de l'appel d'une méthode async ?
graph LR
A[Appel MyAsyncMethod] --> B[Exécution jusqu'à await]
B --> C[Appel DownloadFileAsync]
C --> D{Attente}
D --> |Fichier pas encore| E[Libération du thread]
D --> |Fichier prêt| F[Exécution après await]
F --> G[Fin de la méthode]
- Jusqu'au premier await la méthode s'exécute de manière synchrone.
- Au await l'exécution de la méthode est suspendue, le contrôle revient au code appelant.
- Quand l'opération asynchrone est terminée, l'exécution reprend après le await — comme si de rien n'était.
3. Exemple : téléchargement asynchrone avec async/await
Supposons que notre application pédagogique télécharge maintenant du texte depuis internet et affiche sa taille sans bloquer le reste du code.
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
// Méthode asynchrone (retourne Task)
public static async Task DownloadAndPrintLengthAsync(string url)
{
Console.WriteLine("On commence le téléchargement...");
// On utilise HttpClient — il supporte les méthodes asynchrones
using (var client = new HttpClient())
{
string data = await client.GetStringAsync(url);
Console.WriteLine($"Téléchargement terminé ! Longueur du texte : {data.Length} caractères.");
}
Console.WriteLine("Le travail de la méthode est terminé.");
}
static void Main()
{
// On lance l'opération asynchrone et on attend sa fin
var task = DownloadAndPrintLengthAsync("https://www.example.com");
task.Wait(); // Pour de simples exemples console ça passe. Dans de vraies applis UI ou web cet appel bloquera et peut causer des deadlocks.
}
}
Explications :
- DownloadAndPrintLengthAsync — entièrement asynchrone, grâce à async et await.
- À l'intérieur de la méthode on attend la fin du téléchargement asynchrone de la chaîne avec await.
- Dans Main() on lance la task et on attend explicitement sa fin via Wait(). En C# moderne on peut rendre Main asynchrone (async Task Main) et simplement écrire await.
4. Comment ça marche ?
Différence entre code synchrone et asynchrone
VARIANTE SYNCHRONE
Console.WriteLine("Début");
string data = client.GetStringAsync(url).Result; // .Result bloque le thread !
Console.WriteLine("Opération terminée");
VARIANTE ASYNCHRONE
Console.WriteLine("Début");
string data = await client.GetStringAsync(url); // Le thread n'est pas bloqué
Console.WriteLine("Opération terminée");
Comment fonctionne await "sous le capot" ?
Quand vous utilisez await, C# « découpe » automatiquement votre méthode en deux (ou plusieurs) parties : tout ce qui est avant le await, et tout ce qui est après. Quand la méthode asynchrone appelée retourne un Task, votre méthode retourne au code appelant, et quand le Task est complété — l'exécution reprend après le await. Tout cela est automatique ; vous n'avez pas à gérer les threads et les switches vous-même.
Fait intéressant : C# transforme votre méthode asynchrone en une machine à états, où chaque await est un nouveau "point de reprise".
Usage de async/await dans les applications réelles
static async Task Main(string[] args)
{
var downloadTask = DownloadAndPrintLengthAsync("https://www.example.com");
// Pendant le téléchargement, on fait autre chose
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"On travaille... itération {i}");
await Task.Delay(500); // Pause 0,5s, simule du travail
}
await downloadTask; // On attend la fin du téléchargement
}
Maintenant le programme télécharge et ne se bloque pas : multitâche "vivant", presque comme un chat qui dort et surveille sa gamelle en même temps.
Asynchronie ≠ multithreading
Différence importante souvent confondue par les débutants : le code asynchrone peut s'exécuter sur le même thread ! L'asynchronie consiste à ne pas bloquer le thread, pas forcément à en créer des nouveaux. Les threads OS sont coûteux ! L'asynchronie permet de "libérer" le thread pour qu'il continue à faire autre chose pendant les opérations longues (réseau, fichiers, timers, etc.).
Tableau comparatif : quand choisir quoi ?
| Scénario | Thread/Task | async/await |
|---|---|---|
| Tâches CPU-bound | Oui | Oui (via Task.Run) |
| Tâches I/O-bound | Peu efficace | Idéal |
| Beaucoup de parallélisme | Compliqué | Simple |
| Simplicité du code | Hardcore | Facile à lire |
5. Nuances utiles
Main asynchrone
Avec les versions modernes de C# on peut rendre la méthode Main asynchrone !
static async Task Main(string[] args)
{
// Tout votre code asynchrone peut être await-é directement dans Main
await DownloadAndAnalyzeFileAsync("https://example.com/file");
}
Erreur typique : oublié le await — la task "s'échappe"
SomeAsyncFunction(); // on n'await pas, personne n'attend la fin !
En conséquence cette tâche s'exécutera "en arrière-plan" — et si une erreur survient, vous ne l'apprendrez même pas !
Quand il ne faut PAS écrire des méthodes async
- Si à l'intérieur de la méthode il n'y a aucune opération asynchrone (aucun await), inutile de la marquer async.
- Évitez d'écrire des méthodes en async void (sauf si vous êtes dans un handler d'événement).
Règles courtes et questions fréquentes
- Peut-on utiliser await en dehors d'une méthode async ? Non ! Toujours seulement à l'intérieur de méthodes marquées async.
- Peut-on avoir plusieurs await dans une même méthode ? Oui, autant que nécessaire — ce sont autant de "points d'attente".
- Et si je veux retourner un résultat ? Utilisez Task<T> et faites un return.
- Peut-on combiner plusieurs tâches asynchrones ? Bien sûr ! On peut lancer plusieurs tasks en parallèle et attendre qu'elles finissent avec Task.WhenAll.
6. Erreurs typiques lors de l'utilisation de async/await
Erreur n°1 : "boule de feu" — await manquant après l'appel d'une task asynchrone.
DownloadAndPrintLengthAsync("https://www.example.com");
Console.WriteLine("Tout est fait !"); // En réalité le téléchargement est encore en cours !
Le code NE attend PAS la fin de l'opération asynchrone. Toutes les tâches asynchrones doivent soit être await-ées, soit explicitement attendues via Wait() (mais cette seconde option est dangereuse et peut causer des blocages).
Erreur n°2 : mélange du code synchrone et asynchrone via .Result ou .Wait().
C'est un anti-pattern qui annule tous les avantages de l'asynchronie. Dans les applications UI ou ASP.NET cela mène presque à coup sûr à des deadlocks : la tâche asynchrone attend la libération du thread, tandis que le thread est bloqué en attendant la fin de la tâche. Retenez la mantra : "async all the way" (asynchrone jusqu'au sommet).
Erreur n°3 : Utiliser async void.
Les méthodes async void ne peuvent pas être await-ées, les exceptions lancées depuis elles ne peuvent pas être capturées avec un try-catch standard et conduisent généralement à la terminaison abrupte de l'application. Le seul usage acceptable de async void est pour les handlers d'événements (par exemple, async void Button_Click(...)), quand la signature l'exige. Dans tous les autres cas utilisez async Task.
Erreur n°4 : async superflu dans une méthode sans await.
Si vous marquez une méthode async mais n'utilisez pas await à l'intérieur, le compilateur émettra un avertissement. Ce code s'exécutera totalement de manière synchrone, mais avec un surcoût inutile lié à la création d'une "machine à états". Cela induit en erreur et réduit les performances.
GO TO FULL VERSION