0. Introduction

Le background worker est un composant inclus dans le Framework .Net et qui permet d'exécuter très facilement du code dans un autre thread que le thread principal de votre application quelque soit son type. En effectuant des opérations dans un background worker on empêche ainsi à une application d'être figée le temps de réaliser une requête à une base de données, un appel à une ressource externe ou même des calculs complexes… Vous en conviendrez, il est très pénible de voir une application figée lors d'un long traitement.

La solution pour éviter ce genre de désagrément consiste à exécuter le code du traitement long à réaliser dans un autre thread que le thread principal de l'application, le Framework possède pour cela une classe Thread. Cette classe bien que pleinement fonctionnelle n'est pas simple à utiliser. Conscient de ce problème Microsoft a alors ajouté un composant prêt à l'emploi, le background worker, voyons comment il fonctionne et comment l'utiliser.

I. Utilisation du background worker

Le background worker est un composant que l'on peut utiliser indifféremment de manière graphique par glisser/déposer sur un formulaire Windows par exemple ou alors en le déclarant directement dans le code.

Instanciation d'un Background worker
Sélectionnez

BackgroundWorker bgw1 = new BackgroundWorker();

bgw1.DoWork += new DoWorkEventHandler(bgw1_DoWork);
bgw1.ProgressChanged += new ProgressChangedEventHandler(bgw1_ProgressChanged);
bgw1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw1_RunWorkerCompleted);

Après avoir instancié le background worker il suffit d'appeler la méthode RunWorkerAsync pour lancer le traitement asynchrone, cela signifie que le code associé est exécuté dans un thread dédié et qu'il ne provoque donc pas un blocage ou un ralentissement de l'affichage et des traitements du thread principal. La méthode RunWorkerAsync prend un paramètre qui est de type object, cela laisse donc le loisir de passer ce que l'on veut au thread qui exécutera le code. Nous verrons plus tard dans ce tutoriel comment récupérer un résultat.

Le composant background worker possède 3 évènements qui vont nous servir à placer le code que l'on souhaite exécuter, a détecter la progression ainsi que la fin de cette exécution. Il faut s'abonner à ces 3 évènements pour pouvoir tirer parti du composant.

II. Les évènements du background worker

Les events handler du background worker
Sélectionnez

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            
        }

II-A. Disposed

Inutile de s'étendre sur cet évènement, vous devez simplement savoir que celui-ci est levé lorsque le composant est "disposé" suite à l'appel de la méthode Dispose().

II-B. DoWork

DoWork est l'évènement principal de ce composant, en effet il est levé à la suite de l'appel de la méthode RunWorkerAsync, méthode qui permet de lancer le traitement aynschrone du worker. Le code se trouvant dans l'event handler de l'évènement DoWork est celui qui sera exécuté dans le thread en arrière plan, c'est donc ici que l'on doit placer le code que l'on souhaite exécuter en tâche de fond de l'application. Voici comment s'abonner à cet évènement :

Si l'on souhaite passer un paramètre à notre worker il suffit d'utiliser la surcharge de la méthode RunWorkerAsync ayant un paramètre de type "object". Dans l'event handler l'on retrouver cet objet dans la propriété "Argument" du paramètre "e" qui est de type DoWorkEventArgs. Etant donné que cette propriété est de type " object " il ne faudra pas oublier de " caster " ce paramètre vers le type souhaité. Le DoWorkEventArgs possède en plus une propriété " Result " de type " object " qui permet de retourner un résultat depuis le Thread en arrière plan, libre à vous de placer ce que vous voulez dans cet objet.

Vous l'aurez compris, l'event handler de l'évènement DoWork, comme son nom l'indique, sert à contenir le code exécuter en arrière plan, il peut prendre des paramètres en entrée et retourner un résultat par l'intermédiaire du DoWorkEventArgs.

II-C. ProgressChanged

Le ProgressChanged est un évènement qui peut être levé depuis le code exécuté en arrière plan, il permet de retourner à l'application ayant lancé le background worker des informations sur progression du traitement. Cet évènement pourra servir par exemple à rafraichir une barre ou l'affichage indiquant la progression. Cet évènement peut être désactivé par l'intermédiaire de la propriété WorkerReportsProgress en la positionnant à False.

Pour levé cet évènement il suffit d'appeler la méthode ReportProgress depuis le code exécutant le traitement et de passer en paramètre le pourcentage de l'avancement (un Int32) et éventuellement un autre paramètre de type object qui permet de retourner d'autres informations si nécessaire, à cet effet la méthode ReportProgress propose deux signatures :

  • ReportProgress(int percentProgress)
  • ReportProgress(int percentProgress, object userState)

Le code se trouvant dans l'event handler de l'évènement ProgressChanged pourra alors récupérer ces informations dans les propriétiés de l'objet ProgressChangedEventArgs.

II-D. RunWorkerCompleted

Le troisième évènement important du background worker est le RunWorkerCompleted qui est levé automatiquement par le composant quand le traitement est terminé (sortie du event handler de l'évènement DoWork). Lorsque cet évènement est levé alors la propriété IsRunning du background worker passe à False. Comme les autres évènements celui-ci possède son propre RunWorkerCompletedEventArgs qui permet entre autre de récupérer la propriété Result de type object accessible depuis le DoWork qui permet de retourner le résultat du traitement.

III. Annulation d'un traitement en cours

Une fonctionnalité intéressante de ce background worker est le fait qu'il est possible d'annuler un traitement en cours si l'on a préalablement positionné la propriété WorkerSupportsCancellation du worker à True. En effet si un traitement prend trop de temps ou pour n'importe quel raison vous souhaitez l'interrompre il suffit simplement d'appeler là méthode CancelAsync(). Cependant il n'y a pas de magie, cette méthode ne fait que positionner la propriété CancellationPending à True, c'est alors à vous de vérifier périodiquement cette propriété dans le DoWork et de prévoir le code de sortie si celle-ci n'est plus à False. Cela est très facile si votre algorithme est basé sur une boucle, cela devient un peu plus délicat dans le cas d'une connexion à une base de données par exemple, il faut alors créer le code qui va périodiquement vérifier la propriété et libérer proprement toutes les ressources utilisées avant de quitte le DoWork.

Attention, l'appel à la méthode CancelAsync n'a pas forcément une action immédiate sur le positionnement de la propriété CancellationPending à True, du fait du caracètre multi-threadé de ce composant il peut s'écouler un certain moment entre l'appel de cette méthode et l'a modification effective.

Conclusion

Voilà nous en avons terminé pour cette présentation du composant Background Worker qui permet de réalisé très facilement des traitements en arrière plan de votre application, en plus les différents EventArgs et surcharges de méthodes permettent de passer des paramètres, vérifier la progression et récupérer un résultat sans difficultés. Je vous recommande la lecture du MSDN si vous souhaitez avoir plus d'informations sur ce composant, mais vous êtes désormais aptes à utiliser le background worker dans l'immense majorité des cas, n'hésitez pas à en abusez, en effet rien n'est plus frustrant de voir une application figée lors de traitement long sans pour autant savoir si l'application fonctionne toujours ou est simplement plantée.