Passer au contenu du pied de page
Iron Academy Logo
Apprendre le C#
Apprendre le C#

Autres catégories

Maîtriser C# Async et Await

Tim Corey
38m 57s

La programmation asynchrone peut sembler décourageante en raison de sa terminologie complexe, mais elle offre des avantages significatifs en rendant les applications plus rapides et plus réactives. La vidéo de Tim Corey intitulée "C# Async / Await - Make your app more responsive and faster with asynchronous programming" fournit un guide pratique sur l'utilisation efficace de ces fonctionnalités.

Dans cet article, nous utiliserons la vidéo détaillée de Tim Corey pour décomposer ces concepts, en démontrant les applications pratiques de async et await à travers des exemples clairs. Nous couvrirons différents scénarios, des gestionnaires d'événements asynchrones à la gestion de tâches multiples, en passant par les contextes de synchronisation, la gestion des erreurs et les pièges courants tels que l'utilisation de méthodes asynchrones nulles (async void). À la fin de cet article, vous aurez une solide compréhension de la programmation asynchrone et de la manière de la mettre en œuvre dans vos propres applications C#.

Introduction

La programmation asynchrone en C# est essentielle pour améliorer les performances des applications, notamment lorsqu'il s'agit d'opérations liées aux E/S ou au processeur. Les mots-clés async et await permettent aux développeurs d'écrire du code asynchrone qui peut exécuter des tâches de longue durée sans bloquer le thread principal. En marquant une méthode avec le mot-clé async, vous indiquez que la méthode effectuera une opération asynchrone, renvoyant une tâche ou Task<t>. Le mot-clé await est utilisé dans une méthode async pour suspendre l'exécution jusqu'à ce que la tâche attendue soit terminée, permettant ainsi à d'autres opérations de s'exécuter simultanément.

Contrairement à la programmation synchrone, où les tâches s'exécutent de manière séquentielle et peuvent bloquer le fil d'exécution, les méthodes asynchrones vous permettent de gérer plusieurs tâches simultanément, telles que les processus d'arrière-plan ou le traitement des entrées utilisateur, sans bloquer le fil d'exécution de l'interface utilisateur. Dans cet article, nous explorerons comment async et await fonctionnent ensemble, abordant des concepts tels que les méthodes async void, la gestion des erreurs, les commutations de contexte et les contextes de synchronisation. Vous apprendrez à gérer efficacement plusieurs opérations asynchrones et à gérer les exceptions, en garantissant une exécution fluide entre le thread d'appel, le thread de l'interface utilisateur et l'achèvement de la tâche. Cette approche est particulièrement utile pour les scénarios impliquant des systèmes de fichiers, des requêtes réseau ou des requêtes concurrentes où la réactivité est essentielle.

Tim présente la programmation asynchrone en expliquant la différence entre les opérations synchrones et asynchrones. Dans la programmation synchrone, les tâches sont exécutées de manière séquentielle, ce qui entraîne des retards si une tâche prend beaucoup de temps. La programmation asynchrone permet d'exécuter des tâches en parallèle ou en arrière-plan, ce qui améliore les performances et la réactivité.

Avantages de la programmation asynchrone

Tim souligne deux avantages principaux de la programmation asynchrone :

  1. <Amélioration de la réactivité de l'interface utilisateur : en exécutant des tâches de manière asynchrone, l'interface utilisateur reste réactive même lors de l'exécution d'opérations de longue durée.

  2. Exécution parallèle : des tâches indépendantes peuvent être exécutées en parallèle, ce qui réduit le temps total nécessaire à leur réalisation.

Démonstration d'une application

Tim met en place une application de démonstration Windows Presentation Foundation (WPF) pour démontrer la différence entre les opérations synchrones et asynchrones. L'application comporte deux boutons : l'un pour exécuter des tâches de manière synchrone et l'autre pour les exécuter de manière asynchrone.

Application Démo

L'application comprend également un panneau de résultats pour afficher les résultats.

Opérations synchrones

Tim explique le code qui sous-tend l'opération synchrone. Un Stopwatch est utilisé pour mesurer le temps d'exécution de la tâche.

private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}

Ce code effectue les étapes suivantes :

  1. Démarrer le chronomètre : Mesure le temps d'exécution.
  2. <Obtenir la liste des sites web : Récupère une liste d'URL de sites web.
  3. <Télécharger chaque site web : Permet de télécharger le contenu de chaque site web.
  4. Rapport d'informations sur le site web : Affiche l'URL et la longueur du contenu téléchargé.
  5. Stop the Stopwatch : arrête le chronomètre et indique le temps d'exécution total.

Observation de l'opération synchrone

Tim exécute l'application pour démontrer le fonctionnement synchrone. Il note que l'interface utilisateur ne répond plus pendant le téléchargement des sites web et que les résultats s'affichent tous en même temps une fois les téléchargements terminés.

Opération Synchrone

Pour une démonstration pratique du formulaire qui ne bouge pas, regardez la vidéo à 9:20 pour une meilleure compréhension.

Création d'une tâche asynchrone

Tim s'attaque au premier problème en convertissant la méthode synchrone en méthode asynchrone. Cela implique l'utilisation des mots-clés async et await. Voici une explication étape par étape du processus :

  1. Copie d'une méthode synchrone existante :

    • Tim copie la méthode synchrone existante et la renomme pour indiquer qu'elle sera asynchrone.
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
  2. Modifier l'appel au téléchargement pour une exécution asynchrone :

    • Tim encapsule l'appel de téléchargement dans un Task.Run pour l'exécuter de manière asynchrone.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    • Le mot-clé await garantit que la méthode attend que la tâche asynchrone soit terminée avant de continuer.
  3. S'assurer que la méthode est asynchrone :

    • La signature de la méthode est mise à jour pour inclure le mot-clé async, et elle renvoie une tâche.
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
  4. Gérer correctement l'événement :

    • Le gestionnaire d'événement du clic sur le bouton est mis à jour pour appeler la nouvelle méthode asynchrone.
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    • Notez que les gestionnaires d'événements peuvent renvoyer void même s'ils appellent des méthodes asynchrones.

S'attaquer à la réactivité de l'interface utilisateur

Tim montre qu'en utilisant le mot-clé await, l'interface utilisateur reste réactive. Cela permet aux utilisateurs d'interagir avec la fenêtre pendant que la tâche asynchrone est en cours d'exécution.

// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

Assurer un timing correct

Pour garantir que le temps total d'exécution est correctement rapporté, Tim ajoute le mot-clé await à l'appel dans le gestionnaire d'événements de clic du bouton.

// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

En attendant que la méthode asynchrone se termine, le temps d'exécution est mesuré avec précision et les résultats sont affichés au fur et à mesure qu'ils sont récupérés.

Créer des asynchronismes parallèles

Tim s'attaque aux limites de l'attente séquentielle de chaque tâche en utilisant l'exécution parallèle. Voici comment il modifie le code pour y parvenir :

  1. Copier la méthode asynchrone existante :

    • Tim duplique la méthode asynchrone existante et la renomme pour indiquer une exécution parallèle.
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
  2. Créer une liste de tâches :

    • Une liste de tâches est créée pour stocker toutes les tâches de téléchargement.
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
  3. Initialiser toutes les tâches sans attendre :

    • Au lieu d'attendre immédiatement chaque tâche de téléchargement, Tim l'ajoute à la liste des tâches.
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
  4. Attendre que toutes les tâches soient terminées :

    • La méthode Task.WhenAll est utilisée pour attendre la complétion de toutes les tâches. Cette méthode renvoie un tableau de résultats une fois que toutes les tâches sont terminées.
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
  5. Traiter les résultats :

    • Une fois toutes les tâches terminées, Tim traite les résultats en boucle.
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }

Voici le code complet de la méthode d'exécution parallèle :

private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}
private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}

Mise à jour du gestionnaire d'événements

Tim met à jour le gestionnaire d'événement du clic sur le bouton pour appeler la nouvelle méthode d'exécution parallèle.

private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

En attendant RunDownloadParallelAsync, le temps d'exécution total est mesuré et rapporté avec précision.

Observer l'amélioration des performances

Tim démontre l'amélioration des performances en exécutant l'application en parallèle. Les résultats montrent une réduction significative du temps d'exécution total par rapport à l'exécution séquentielle.

// Parallel execution
async Task RunDownloadParallelAsync();
// Parallel execution
async Task RunDownloadParallelAsync();

L'augmentation de la vitesse est évidente lorsque les sites web sont téléchargés simultanément, ce qui réduit le temps d'attente total à la durée du téléchargement individuel le plus lent.

Encapsulation de la Méthode dans Task.Run() vs Appel de Méthode Asynchrone

Tim explique le concept d'utilisation de Task.Run() pour encapsuler une méthode synchrone et la rendre asynchrone quand vous ne pouvez pas modifier la méthode originale. Cependant, il montre également l'approche préférée qui consiste à modifier la méthode elle-même pour qu'elle soit asynchrone si vous en avez le contrôle.

  1. Encapsulation de la Méthode dans Task.Run() :

    • Cette approche est utile lorsque vous ne pouvez pas modifier le code de la méthode originale mais que vous souhaitez néanmoins l'exécuter de manière asynchrone.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
  2. <Rendre une méthode asynchrone :

    • Si vous pouvez modifier la méthode, il est préférable de convertir la méthode elle-même en méthode asynchrone en utilisant une API asynchrone appropriée.

    • Tim démontre cela en changeant DownloadWebsite en DownloadWebsiteAsync et en utilisant DownloadStringTaskAsync de WebClient.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
  3. Adaptation de la méthode d'appel :

    • Après avoir converti la méthode, le code appelant doit être ajusté pour retirer l'encapsulation Task.Run() et appeler directement la méthode asynchrone.
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }

Résumé des points clés

Tim conclut avec plusieurs points importants à retenir pour l'utilisation d'async et d'await :

  • Assurez-vous que les Méthodes Renvoient des Tâches : Chaque fois que vous marquez une méthode comme async, elle doit retourner un Task ou Task<t> au lieu de void (sauf pour les gestionnaires d'événements).

  • Utilisez await pour des Opérations Fiables : Utilisez le mot-clé await lorsque vous avez besoin du résultat d'une opération asynchrone avant de continuer.

  • Encapsulation de Tâche pour Code Non Modifiable : Utilisez Task.Run() pour encapsuler des méthodes synchrones lorsque vous ne pouvez pas modifier la méthode originale.

  • Marquez les Méthodes Asynchrones de Manière Appropriée : Ajoutez toujours Async au nom de la méthode pour indiquer qu'il s'agit d'une opération asynchrone.

  • Exécution parallèle ou séquentielle : Décidez si les tâches peuvent être exécutées en parallèle ou si elles doivent attendre l'une après l'autre en fonction des dépendances entre les tâches.

Tim souligne que la programmation asynchrone en C# a été simplifiée avec async, await et Task. Les complexités telles que les contextes de threading et les modèles d'appartements sont gérées en coulisses.

Conclusion

Le tutoriel de Tim Corey sur async et await en C# constitue une ressource inestimable pour maîtriser la programmation asynchrone. En expliquant soigneusement des concepts tels que le parallélisme des tâches, la réactivité de l'interface utilisateur et l'importance d'utiliser async et await dans le bon contexte, Tim fournit aux développeurs les outils nécessaires pour créer des applications plus rapides et plus réactives. Sa présentation détaillée montre comment gérer efficacement les tâches de longue durée sans bloquer le thread principal, ainsi que les stratégies de gestion des opérations asynchrones et de l'exécution parallèle.

Pour une compréhension plus approfondie et des exemples pratiques supplémentaires, je recommande vivement de regarder la vidéo de Tim Corey. Sa vision des méthodes asynchrones void, de la gestion des erreurs et des contextes de synchronisation vous permettra d'améliorer votre capacité à mettre en œuvre ces concepts dans vos projets.

Hero Worlddot related to Maîtriser C# Async et Await
Hero Affiliate related to Maîtriser C# Async et Await

Gagnez plus en partageant ce que vous aimez

Vous créez du contenu pour les développeurs travaillant avec .NET, C#, Java, Python ou Node.js ? Transformez votre expertise en revenu supplémentaire !

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi