Programmation asynchrone avec progression en .NET

msDotNetLogo

La programmation asynchrone est de plus en plus utilisée aujourd’hui, autant pour améliorer les performances que pour améliorer la « fluidité » des applications.

Au fil des années, le Framework .NET s’est enrichi de nouvelles fonctionnalités facilitant l’accès à la programmation asynchrone. L’arrivée des mots clés « async » et « await » dans le Framework 4.5 ont grandement simplifié son utilisation.

Dans cet article, nous allons étudier un exemple simple de programmation asynchrone, avec affichage de la progression. Au sein d’une application Winform C#.NET, il s’agit d’effectuer un traitement quelconque (ici attendre 10 secondes), sans bloquer l’interface utilisateur. Une textbox « textbox1 » nous servira pour afficher la trace.

Appel synchrone

Dans notre écran, lançons pour commencer notre traitement de manière classique, c’est-à-dire en « synchrone ».

 private void button1_Click(object sender, EventArgs e)
        {
            this.textBox1.Text += "\r\n Début procédure principale";            
            LancerTraitement();
            this.textBox1.Text += "\r\n Fin procédure principale";            
        }

        
        public void LancerTraitement()
        {
            this.textBox1.Text += "\r\n Début traitement";
            
            AttendreDixSecondes();

            this.textBox1.Text += "\r\n Fin traitement";

        }
        

        public void AttendreDixSecondes()
        {
            //Attend 10 fois 1 seconde    	            	                		
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(1000);
            }
        }

Résultat :

  • Lors du click sur le bouton, l’interface utilisateur est bloquée pendant 10 secondes. Pendant ce laps de temps, l’utilisateur n’a plus la main dans l’application, le code attend la fin de l’exécution de la procédure « AttendreDixSecondes» avant de passer à l’instruction suivante.
  • Une fois les 10 secondes écoulées, le message « Suite procédure principale » s’affiche.
  • Notre « textbox1 » affiche donc, dans cet ordre :
    • Début procédure principale
    • Début traitement
    • Fin traitement   (au bout de 10 secondes de "blocage")
    • Suite procédure principale

 

Appel asynchrone

Lançons maintenant le même traitement de manière asynchrone.

Le click du bouton appelle maintenant la méthode LancerTraitementAsync :

       private void button1_Click(object sender, EventArgs e)
        {
            this.textBox1.Text += "\r\n Début procédure principale";            
            LancerTraitementAsync();
            this.textBox1.Text += "\r\n Suite procédure principale";            
        }

Cette méthode « LancerTraitementAsync » appelle notre fonction « AttendreDixSecondes» de manière asynchrone :
 public async void LancerTraitementAsync()
        {
            this.textBox1.Text += "\r\n Début traitement";
            
            await Task.Run(() => AttendreDixSecondes());

            this.textBox1.Text += "\r\n Fin traitement";

        }

On notera qu’elle possède dans sa définition le mot clé « async » (lancement en asynchrone), ainsi qu’une instruction « await » indiquant, au sein de cette fonction asynchrone, d’attendre la fin de l’exécution de la méthode « AttendreDixSecondes» avant de continuer.

La méthode « Attendre 10 secondes » reste la même :

 public void AttendreDixSecondes()
        {
            //Attend 10 fois 1 seconde    	            	                		
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(1000);
            }
        }

Résultat :

  • Lors du click sur le bouton, on appelle la méthode «LancerTraitementAsync » : celle-ci affiche le message « Début traitement » dans la textbox, puis attend 10 secondes.
  • «LancerTraitementAsync » étant lancée de manière asynchrone, l’interface utilisateur n’est pas bloquée : la méthode Click continue de se dérouler : le message « Suite procédure principale» s’affiche sans attendre les 10 secondes.
  • A l’issue des 10 secondes, la méthode «LancerTraitementAsync » affiche le message « Fin traitement ».
  • Au final, la texbox affiche, dans cet ordre :
    • Début procédure principale
    • Début traitement
    • Suite procédure principale
    • Fin Traitement   (au bout de 10 secondes, sans avoir "bloqué" l'écran)
  • Par rapport à l’appel synchrone, on constate que la procédure principale n’a pas attendu la fin du traitement pour continuer

En imaginant que le traitement « Attendre10Secondes » corresponde à un chargement de données quelconque, nous avons amélioré la navigation dans l’application en ne bloquant pas l’utilisateur pendant ce temps de chargement. Mieux, nous allons maintenant afficher la progression du chargement, toujours sans bloquer l’utilisateur.

Appel asynchrone avec progression

Depuis le Framework 4.5, l’interface Iprogress<T> a été ajoutée pour informer de l’avancement d’un traitement. Cette interface ne contient qu’un seule méthode : Report<T>, appelée au sein des méthodes asynchrones.

Dans notre exemple, nous allons utiliser cette interface avec un type “entier” pour informer du pourcentage de progression de l’attente.

La méthode « AttendreDixSecondes » doit disposer d’un paramètre de type IProgress<int>, par lequel on fera transiter l’information d’avancement avec la méthode Report() :

 public void AttendreDixSecondes(IProgress<int> progress)
        {
            //Attend 10 fois 1 seconde
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(1000);
                //Rapporte la progression
                if (progress != null)
                    progress.Report((i + 1) * 10);
            }
        }

Au sein de la méthode “lancerTraitementAsync” on initialise une variable « progress » du même type.

On définit par une lambda expression l’action à réaliser quand une information de progression est reçue : ici nous allons afficher le pourcentage d’attente dans notre textbox1, mais la plupart du temps nous utiliserons une barre de progression.

public async void LancerTraitementAsync()
        {
            this.textBox1.Text += "\r\n Début traitement";

            //Mise à jour de la progression dans la textbox par lambda expression
            var progress = new Progress<int>(percent =>
            {
                this.textBox1.Text += String.Format("\r\n Attente : {0} % ", percent);

            });


            //Lancement asynchrone du traitement, en passant en paramètre notre variable de type « Progress ».
            await Task.Run(() => AttendreDixSecondes(progress));

            this.textBox1.Text += "\r\n Fin traitement";
        }

Résultat :

  • La texbox affiche, dans cet ordre :
    • Début procédure principale
    • Début traitement
    • Suite procédure principale
    • Attente : 10 %
    • Attente : 20 %
    • Attente : 30 %
    • Attente : 40 %
    • Attente : 50 %
    • Attente : 60 %
    • Attente : 70 %
    • Attente : 80 %
    • Attente : 90 %
    • Attente : 100 %
    • Fin traitement
  • Nous obtenons le même résultat que précédemment, avec en plus, une progression de l’avancement (s’incrémentant de 10% à chaque seconde d’attente).
  • Pendant ce temps, l’interface utilisateur n’est pas bloquée, l’utilisateur a toujours la main pour cliquer où il le souhaite, ce qui améliore grandement le confort d’utilisation.


Remarque : progression dans l’UI par lambda expression

Il aurait été impossible de mettre à jour la textbox (ou la progressbar) directement depuis la fonction AttendreDixSecondes, car celle-ci est lancée de manière asynchrone donc dans un Thread différent de celui gérant l’interface utilisateur. Nous aurions obtenu le message suivant :

SSI TBASYNC MsgErreur
Le fait d’utiliser une lambda expression permet de capter le contexte « UI », et de gérer l’affichage de la progression dans le même contexte que l’interface utilisateur.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.