Leçon 19 - Programmation Asynchrone
Maîtrisez async/await pour créer des applications qui ne se bloquent pas pendant les opérations longues.
Qu'est-ce que l'asynchrone ?
L'asynchrone permet de faire des tâches longues (téléchargement, requêtes réseau) sans bloquer votre programme. C'est comme commander au restaurant : vous ne restez pas debout à attendre, vous vous asseyez et le serveur vous apporte le plat quand il est prêt.
💡 Deux mots-clés essentiels :
- async : Marque une fonction comme asynchrone
- await : Attend le résultat sans bloquer
Exemple simple : Téléchargement
Comparaison entre synchrone (bloquant) et asynchrone (non-bloquant) :
C#
// ❌ Version synchrone - BLOQUE tout
static void TelechargerSync()
{
Console.WriteLine("Début téléchargement...");
Thread.Sleep(3000); // BLOQUE pendant 3 secondes
Console.WriteLine("Téléchargement terminé");
}
// ✅ Version asynchrone - NE BLOQUE PAS
static async Task TelechargerAsync()
{
Console.WriteLine("Début téléchargement...");
await Task.Delay(3000); // N'bloque PAS
Console.WriteLine("Téléchargement terminé");
}
// Utilisation
static async Task Main()
{
Console.WriteLine("Application démarrée");
// Lance le téléchargement sans attendre
Task tache = TelechargerAsync();
// On peut faire autre chose en attendant!
Console.WriteLine("Je fais autre chose...");
// Attendre que ça finisse
await tache;
Console.WriteLine("Tout est terminé");
}
Retourner une valeur
Les fonctions async peuvent retourner des résultats :
C#
// Fonction async qui retourne un nombre
static async Task<int> CalculerAsync(int a, int b)
{
await Task.Delay(1000); // Simule un calcul long
return a + b;
}
// Fonction async qui retourne un texte
static async Task<string> TelechargerTexteAsync(string url)
{
await Task.Delay(2000); // Simule téléchargement
return $"Contenu de {url}";
}
// Utilisation
static async Task Main()
{
int résultat = await CalculerAsync(5, 3);
Console.WriteLine($"Résultat: {résultat}"); // 8
string texte = await TelechargerTexteAsync("https://example.com");
Console.WriteLine(texte);
}
Exécuter plusieurs tâches en parallèle
Lancer plusieurs opérations en même temps pour gagner du temps :
C#
static async Task<string> TelechargerFichierAsync(string nom)
{
Console.WriteLine($"📥 Téléchargement de {nom}...");
await Task.Delay(2000);
Console.WriteLine($"✅ {nom} téléchargé");
return $"Contenu de {nom}";
}
static async Task ExempleParallele()
{
var chrono = System.Diagnostics.Stopwatch.StartNew();
// ❌ Séquentiel : une après l'autre (lent)
await TelechargerFichierAsync("fichier1.txt");
await TelechargerFichierAsync("fichier2.txt");
await TelechargerFichierAsync("fichier3.txt");
// Temps total: ~6 secondes
Console.WriteLine($"Séquentiel: {chrono.ElapsedMilliseconds}ms\n");
// ✅ Parallèle : tout en même temps (rapide)
chrono.Restart();
Task<string> tache1 = TelechargerFichierAsync("fichier1.txt");
Task<string> tache2 = TelechargerFichierAsync("fichier2.txt");
Task<string> tache3 = TelechargerFichierAsync("fichier3.txt");
// Attendre que TOUTES se terminent
string[] résultats = await Task.WhenAll(tache1, tache2, tache3);
// Temps total: ~2 secondes!
Console.WriteLine($"Parallèle: {chrono.ElapsedMilliseconds}ms");
}
Gérer les erreurs
Utilisez try-catch avec async/await :
C#
static async Task<string> OperationRisqueeAsync(bool causerErreur)
{
await Task.Delay(1000);
if (causerErreur)
{
throw new Exception("Oups, une erreur !");
}
return "Succès";
}
static async Task TesterErreurs()
{
// Gérer une erreur
try
{
string résultat = await OperationRisqueeAsync(true);
Console.WriteLine($"✅ {résultat}");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Erreur: {ex.Message}");
}
// Sans erreur
try
{
string résultat = await OperationRisqueeAsync(false);
Console.WriteLine($"✅ {résultat}");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Erreur: {ex.Message}");
}
}
Timeout et annulation
Annuler une opération qui prend trop de temps :
C#
static async Task OperationLongueAsync(CancellationToken token)
{
for (int i = 1; i <= 10; i++)
{
// Vérifier si annulation demandée
token.ThrowIfCancellationRequested();
Console.WriteLine($"Étape {i}/10");
await Task.Delay(500, token);
}
Console.WriteLine("✅ Opération terminée");
}
static async Task TesterTimeout()
{
// Créer un timeout de 3 secondes
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));
try
{
await OperationLongueAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("⏰ Timeout! Opération annulée après 3 secondes");
}
}
⚡ Règles importantes :
- Toujours utiliser await sur les Task (pas .Result ou .Wait())
- async/await tout le chemin jusqu'au Main()
- Task.WhenAll pour exécuter en parallèle
- CancellationToken pour pouvoir annuler
- try-catch pour gérer les erreurs
Exercice pratique
Créez un simulateur d'API qui télécharge des données utilisateur en parallèle :
C#
class Utilisateur
{
public string Nom { get; set; }
public string Email { get; set; }
}
static async Task<Utilisateur> ChargerUtilisateurAsync(int id)
{
Console.WriteLine($"📡 Chargement utilisateur {id}...");
// Simuler un appel API
await Task.Delay(1000);
return new Utilisateur
{
Nom = $"User{id}",
Email = $"user{id}@example.com"
};
}
static async Task<List<string>> ChargerCommandesAsync(int userId)
{
Console.WriteLine($"📦 Chargement commandes pour user {userId}...");
await Task.Delay(800);
return new List<string> { "Commande1", "Commande2" };
}
static async Task Main()
{
Console.WriteLine("=== Chargement du profil utilisateur ===\n");
var chrono = System.Diagnostics.Stopwatch.StartNew();
// Lancer les 3 opérations en parallèle
var tacheUser = ChargerUtilisateurAsync(1);
var tacheCommandes = ChargerCommandesAsync(1);
var tachePreferences = Task.Delay(600).ContinueWith(_ =>
{
Console.WriteLine("⚙️ Préférences chargées");
return "Theme: Dark, Langue: FR";
});
// Attendre tout en parallèle
await Task.WhenAll(tacheUser, tacheCommandes, tachePreferences);
chrono.Stop();
// Récupérer les résultats
var user = await tacheUser;
var commandes = await tacheCommandes;
var preferences = await tachePreferences;
// Afficher le profil complet
Console.WriteLine("\n=== PROFIL CHARGÉ ===");
Console.WriteLine($"Nom: {user.Nom}");
Console.WriteLine($"Email: {user.Email}");
Console.WriteLine($"Commandes: {commandes.Count}");
Console.WriteLine($"Préférences: {preferences}");
Console.WriteLine($"\n⏱️ Temps total: {chrono.ElapsedMilliseconds}ms");
Console.WriteLine("(Au lieu de ~2400ms si séquentiel!)");
}
/* Résultat:
=== Chargement du profil utilisateur ===
📡 Chargement utilisateur 1...
📦 Chargement commandes pour user 1...
⚙️ Préférences chargées
=== PROFIL CHARGÉ ===
Nom: User1
Email: user1@example.com
Commandes: 2
Préférences: Theme: Dark, Langue: FR
⏱️ Temps total: ~1000ms
(Au lieu de ~2400ms si séquentiel!)
*/