1. Introduction
Imaginez que votre code est une file d'attente au magasin. Si vous lancez une méthode synchrone, toute la file (votre thread) attend que la caissière ait fini avec un client avant de passer au suivant. Si la méthode est asynchrone, vous posez juste vos achats à la caisse et vous allez faire autre chose, et quand la caissière a fini — elle vous appellera.
Une méthode asynchrone (méthode async) en .NET est une méthode normale avec une "étiquette" particulière devant elle (async), qui permet d'utiliser à l'intérieur l'opérateur await pour attendre non bloquant d'autres opérations asynchrones (par exemple, réseau ou fichiers) sans bloquer le thread principal. C'est cette "étiquette" qui transforme un magasin synchrone en un service moderne sans files d'attente.
Signature de base d'une méthode asynchrone
Une méthode asynchrone est obligatoirement déclarée avec le modificateur async dans la signature. Voici l'exemple le plus basique :
public async Task MyAsyncMethod()
{
// Votre code asynchrone ici
}
Types de retour des méthodes asynchrones :
| Type de retour | Description | Exemple |
|---|---|---|
|
La méthode fait le travail de façon asynchrone, mais ne renvoie pas de résultat | |
|
La méthode fait le travail de façon asynchrone et renvoie un résultat de type T | |
|
Utilisé uniquement pour les événements. Pas recommandé ailleurs ! | |
|
Pour des scénarios haute performance, quand le résultat est souvent prêt synchroniquement | — |
Important : N'utilisez pas async void sauf dans les handlers d'événements ! Sinon vous risquez de ne pas gérer correctement les erreurs.
2. Exemple simple de méthode asynchrone
Retour à notre application d'entraînement (disons un simple programme console), et ajoutons une méthode asynchrone qui récupère des données depuis le réseau. On va émuler un délai.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Démarrage de la méthode asynchrone...");
await MyFakeDownloadAsync();
Console.WriteLine("Opération asynchrone terminée !");
}
static async Task MyFakeDownloadAsync()
{
Console.WriteLine("On commence le téléchargement (on émulera un délai de 2 s)...");
await Task.Delay(2000); // On simule une opération longue
Console.WriteLine("Téléchargement terminé !");
}
}
Commentaires sur l'exemple :
- Le mot-clé async est présent sur Main et MyFakeDownloadAsync.
- L'opérateur await ne peut être utilisé qu'à l'intérieur de méthodes marquées comme async.
- La méthode Task.Delay(2000) simule une opération longue (par exemple une requête réseau).
- Après l'apparition de l'opérateur await, le thread n'est pas bloqué — l'exécution de Main est "suspendue" jusqu'à la fin du délai.
3. Comment fonctionne une méthode asynchrone sous le capot ?
Quand le compilateur voit une méthode avec le mot-clé async, il la transforme en une "machine d'état" qui mémorise le point d'arrêt, les variables locales et continue l'exécution après la fin de l'opération awaited.
Voici un schéma très simplifié :
+-----------------------------------------------------------+
| Appel de la méthode asynchrone (par ex. await X) |
+-------------------------+---------------------------------+
|
v
L'opération est-elle terminée ? (pas Task.Completed)
| |
| oui | non
v v
Suspendre Retourner
l'exécution, immédiatement
mémoriser le résultat
le contexte (ou lancer l'exception)
|
L'opération asynchrone se termine...
|
v
La machine d'état "réveille" la méthode,
l'exécution reprend depuis l'endroit après await
Il n'est pas nécessaire de retenir tous les détails d'implémentation : le contexte (point d'arrêt et variables locales) est sauvé et restauré automatiquement quand l'opération se termine.
Où peut-on (et ne peut-on pas) utiliser les mots-clés async et await
- async se place avant la déclaration de la méthode (ou d'une lambda).
- À l'intérieur d'une méthode async on peut (et doit) utiliser await.
- Si vous essayez d'écrire await dans une méthode sans async — vous aurez une erreur de compilation.
- Si vous mettez async mais qu'il n'y a aucun await — vous recevrez un avertissement ; la méthode s'exécutera de façon synchrone et retournera une Task déjà complétée.
4. Variantes des valeurs de retour et leurs particularités
Résultat Task
Si la méthode effectue des actions asynchrones mais ne renvoie pas de valeur, utilisez Task :
public async Task SaveToFileAsync(string path)
{
await Task.Delay(1000);
// On enregistre quelque chose dans le fichier
}
Résultat Task<T>
Si vous avez besoin d'un résultat, utilisez Task<T> :
public async Task<int> CalculateAsync()
{
await Task.Delay(500);
return 42;
}
Appel :
int result = await CalculateAsync();
Résultat async void
N'utilisez que pour les événements ! Par exemple, un handler de clic de bouton :
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
// On fait autre chose
}
Pourquoi ne pas utiliser async void dans des méthodes normales ? Parce que vous ne saurez pas quand une telle méthode se termine, et vous ne pourrez pas attraper correctement les exceptions qui s'y produisent.
Résultats ValueTask et ValueTask<T>
Ils sont apparus pour des bibliothèques haute performance, quand le résultat est souvent déjà prêt synchroniquement — ils permettent d'éviter des allocations Task inutiles. Au début de votre carrière vous pouvez éviter de les utiliser : on les rencontre moins souvent.
5. Exemple : calculateur asynchrone qui renvoie un résultat
Supposons que le programme calcule la somme de deux nombres "avec délai", comme si c'était une opération très lourde :
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Entrez le premier nombre :");
int a = int.Parse(Console.ReadLine()!);
Console.WriteLine("Entrez le deuxième nombre :");
int b = int.Parse(Console.ReadLine()!);
Console.WriteLine("On effectue le calcul...");
int sum = await CalculateSumAsync(a, b);
Console.WriteLine($"Somme : {sum}");
}
static async Task<int> CalculateSumAsync(int x, int y)
{
await Task.Delay(2000); // opération "longue"
return x + y;
}
}
Ce qui est important ici :
- CalculateSumAsync est une méthode asynchrone, renvoie Task<int>, et contient await.
- L'appel dans Main se fait avec await (rappel : depuis C# 7.1 la méthode Main peut être asynchrone).
- Pendant que la somme est calculée, le thread n'est pas bloqué — on peut afficher une barre de progression ou faire d'autres tâches.
6. Méthodes imbriquées et await imbriqués
Une méthode asynchrone peut appeler une autre méthode asynchrone, qui en appelle une autre. Toute la chaîne se poursuit automatiquement :
public async Task<int> StartWorkAsync()
{
int data = await GetDataAsync();
int result = await ProcessDataAsync(data);
return result;
}
C'est simple : on attend la fin d'une opération, puis de l'autre.
7. Pièges et erreurs fréquentes
1. N'oubliez pas async !
Si vous oubliez async, le compilateur se fâchera en voyant await dans la méthode et empêchera la compilation.
2. N'utilisez pas async void dans des méthodes normales
Les méthodes asynchrones en void (sauf les handlers d'événements) sont des "trous noirs" pour les exceptions. Les erreurs dedans peuvent "se noyer" et vous ne le saurez même pas.
3. Méthode asynchrone SANS await
C'est possible, mais ça n'a pas de sens — la méthode s'exécutera de façon synchrone et retournera une Task déjà complétée.
public async Task DoNothingAsync()
{
// Oups, pas d'await !
}
4. Retourner Task depuis une méthode NON-asynchrone
On le fait pour unifier les interfaces : une partie des opérations est vraiment asynchrone, l'autre partie est instantanée.
public Task<int> Foo()
{
// return 42; // On ne peut pas faire ça !
return Task.FromResult(42); // Possible, mais il n'y a aucune asynchronie ici.
}
GO TO FULL VERSION