Chargement...

Bases de la programmation asynchrone en C# (async/await)

Apprenez async et await en C# pour créer des applications réactives avec des tâches asynchrones et des exemples pratiques.

Dans les applications modernes, la programmation asynchrone est essentielle pour garder l’interface utilisateur réactive et exécuter des opérations longues sans bloquer ou geler l’application. En C#, les mots-clés async et await sont utilisés à cet effet. Cette structure permet d’exécuter des tâches de longue durée en arrière-plan tout en maintenant la réactivité de l’application.


Qu’est-ce que la programmation asynchrone ?

La programmation asynchrone permet à d’autres opérations de continuer pendant qu’une tâche est en attente d’exécution. Elle est particulièrement utile pour les opérations I/O-bound telles que la lecture de fichiers, les requêtes réseau, les accès à une base de données ou les appels d’API.


// Synchrone : les opérations s’exécutent l’une après l’autre
LireFichier();
EnvoyerDonnees();
Console.WriteLine("Terminé !");

// Asynchrone : les opérations se poursuivent sans attendre
await LireFichierAsync();
await EnvoyerDonneesAsync();
Console.WriteLine("Terminé !");

Les mots-clés async et await

Le mot-clé async rend une méthode asynchrone. À l’intérieur de cette méthode, le mot-clé await est utilisé pour attendre la fin d’une autre opération asynchrone. Cela permet au thread de continuer son exécution sans être bloqué.


async Task EffectuerOperationAsync()
{
    Console.WriteLine("Opération démarrée...");
    await Task.Delay(2000); // Attend 2 secondes sans bloquer le thread
    Console.WriteLine("Opération terminée !");
}

Dans l’exemple ci-dessus, même si Task.Delay introduit un délai, l’application ne se fige pas car la tâche attend en arrière-plan sans bloquer le processeur.


Types Task et Task<T>

Les méthodes asynchrones retournent généralement un Task ou un Task<T>. Task indique simplement que l’opération est terminée, tandis que Task<T> retourne un résultat.


// Méthode asynchrone sans retour de valeur
async Task EnregistrerFichierAsync(string nom)
{
    await File.WriteAllTextAsync(nom, "Contenu du fichier...");
}

// Méthode asynchrone avec valeur de retour
async Task<string> ObtenirDonneesAsync()
{
    await Task.Delay(1000);
    return "Données reçues du serveur";
}

// Utilisation :
string resultat = await ObtenirDonneesAsync();
Console.WriteLine(resultat);

Méthode Main asynchrone

Depuis C# 7.1, la méthode Main peut également être asynchrone. Cela permet d’utiliser await directement dans une application console.


class Program
{
    static async Task Main()
    {
        Console.WriteLine("Récupération des données...");
        string data = await ObtenirDonneesAsync();
        Console.WriteLine($"Résultat : {data}");
    }

    static async Task<string> ObtenirDonneesAsync()
    {
        await Task.Delay(1500);
        return "Terminé !";
    }
}

Points importants lors de l’utilisation de await


// Exécuter plusieurs tâches en parallèle
var tache1 = LireFichierAsync();
var tache2 = EnvoyerDonneesAsync();
await Task.WhenAll(tache1, tache2);
Console.WriteLine("Les deux tâches sont terminées.");

Gestion des erreurs dans le code asynchrone

Les erreurs dans les méthodes asynchrones peuvent être gérées à l’aide de blocs try-catch. Si une tâche en attente via await lève une exception, le bloc catch sera exécuté.


try
{
    await LireFichierAsync();
}
catch (IOException ex)
{
    Console.WriteLine($"Erreur lors de la lecture du fichier : {ex.Message}");
}

Exemple : Récupération de données depuis une API

L’exemple suivant montre une requête HTTP asynchrone en utilisant HttpClient. Le mot-clé await empêche l’application de se figer pendant l’attente de la réponse du réseau.


using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using HttpClient client = new HttpClient();
        string url = "https://jsonplaceholder.typicode.com/posts/1";
        Console.WriteLine("Requête en cours...");

        string json = await client.GetStringAsync(url);
        Console.WriteLine("JSON reçu :");
        Console.WriteLine(json);
    }
}

Exemple WPF : Opération asynchrone et mise à jour de l’interface

Dans l’exemple suivant, lorsqu’un bouton est cliqué dans une application WPF, une opération longue (simulée avec Task.Delay) est exécutée de manière asynchrone. Grâce au mot-clé await, l’interface utilisateur reste réactive pendant l’exécution, et le contenu du TextBox peut être mis à jour sans geler l’application.


// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Exemple Async / Await" Height="250" Width="400">
    <Grid Margin="20">
        <StackPanel>
            <TextBlock Text="Exemple d’opération asynchrone" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
            <TextBox x:Name="txtStatut" Height="30" Margin="0,0,0,10" 
                     VerticalContentAlignment="Center" FontSize="14"/>
            <Button x:Name="btnDemarrer" Height="35" Content="Démarrer l’opération" 
                    Click="btnDemarrer_Click" FontSize="14"/>
        </StackPanel>
    </Grid>
</Window>

// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;

namespace AsyncExample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnDemarrer_Click(object sender, RoutedEventArgs e)
        {
            // async void est autorisé ici car c’est un gestionnaire d’événement
            txtStatut.Text = "Opération démarrée...";
            btnDemarrer.IsEnabled = false; // désactiver le bouton

            // Simulation d’une opération longue (exemple : téléchargement de fichier)
            await Task.Delay(3000);

            txtStatut.Text = "Opération terminée !";
            btnDemarrer.IsEnabled = true;
        }
    }
}

// Déroulement :
1. L’utilisateur clique sur le bouton « Démarrer l’opération ».
2. Le TextBox affiche « Opération démarrée... ».
3. Attente de 3 secondes (l’interface reste réactive).
4. Une fois terminé, le TextBox affiche « Opération terminée ! ».

Remarque : Si ce code était exécuté de manière synchrone (par exemple avec Thread.Sleep()), l’interface serait figée, et le texte « Opération démarrée... » ne s’afficherait qu’à la fin. L’utilisation de await Task.Delay() évite complètement ce problème.


Exemple WPF : Mise à jour du TextBox depuis une tâche (avec Dispatcher)

Dans cet exemple, une opération longue est exécutée dans une tâche Task séparée. Les mises à jour de txtStatut.Text sont effectuées sur le thread de l’interface grâce à Dispatcher.Invoke. Cela évite les erreurs d’accès multi-threads.


// MainWindow.xaml
<Window x:Class="AsyncExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Exemple Dispatcher" Height="250" Width="400">
    <Grid Margin="20">
        <StackPanel>
            <TextBlock Text="Mise à jour de l’interface dans une tâche" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
            <TextBox x:Name="txtStatut" Height="30" Margin="0,0,0,10"
                     VerticalContentAlignment="Center" FontSize="14"/>
            <Button x:Name="btnDemarrer" Height="35" Content="Démarrer une opération longue"
                    Click="btnDemarrer_Click" FontSize="14"/>
        </StackPanel>
    </Grid>
</Window>

// MainWindow.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;

namespace AsyncExample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnDemarrer_Click(object sender, RoutedEventArgs e)
        {
            btnDemarrer.IsEnabled = false;
            txtStatut.Text = "Processus démarré...";

            // Exécution de l’opération longue dans une tâche séparée
            await Task.Run(() =>
            {
                for (int i = 1; i <= 5; i++)
                {
                    // Simulation d’un travail
                    Task.Delay(1000).Wait();

                    // Mise à jour de l’interface via Dispatcher
                    Dispatcher.Invoke(() =>
                    {
                        txtStatut.Text = $"Étape {i} terminée...";
                    });
                }
            });

            txtStatut.Text = "Toutes les étapes sont terminées !";
            btnDemarrer.IsEnabled = true;
        }
    }
}

// Déroulement :
1. L’utilisateur clique sur le bouton « Démarrer une opération longue ».
2. Un processus en 5 étapes démarre dans Task.Run.
3. Chaque seconde, le TextBox est mis à jour avec Dispatcher.Invoke.
4. L’interface reste réactive pendant tout le processus.

Remarque : Si Dispatcher.Invoke() était remplacé par un simple txtStatut.Text = ..., une erreur se produirait en raison d’un accès depuis un thread non-UI. Alternativement, Dispatcher.BeginInvoke() peut être utilisé, ce qui n’empêche pas le thread de l’interface de continuer.


TL;DR (Résumé)

  • async rend une méthode asynchrone, await attend la fin de l’opération.
  • Task et Task<T> représentent les opérations asynchrones et leurs résultats.
  • await ne bloque pas le processeur ; l’exécution continue après la fin de la tâche.
  • Avec Task.WhenAll(), plusieurs tâches peuvent être exécutées simultanément.
  • Les erreurs sont gérées avec try-catch ; async void ne doit être utilisé que dans les gestionnaires d’événements.

Articles connexes