Mise à jour de l'IHM depuis un Thread

Un petit tutoriel d'initiation à la problématique fréquente de mise à jour d'une IHM pour rendre compte de l'évolution d'un traitement longuet. Vu 972  fois

 

Scénario type

 

On souhaite effectuer un traitement potentiellement long, en informant l'utilisateur du déroulement de celui-ci.

 

Approche 1 : ça bloque !

 

Le principe de base est d'effectuer les tâches et d'intercaler des raffraichissement de l'IHM.

 

private void MaMethode() 
{
this.EffectuerTache1();
this.MettreAJour(champ1, message1, valeur1);
this.Refresh();

this.EffectuerTache2();
this.MettreAJour(champ2, message2, valeur2);
this.Refresh();

this.EffectuerTache3();
this.MettreAJour(champ3, message3, valeur3);
this.Refresh();

...
}

private void MettreAJour(Control champ, string message, int valeur)
{
// Le contenu de cette méthode est volontairement non compilatoire
// A vous de déterminer quelle signature vous voulez utiliser pour mettre à jour vos contrôles
champ.Text = message;
champ.Value = valeur;
this.Refresh();
}

 

L'inconvénient de cette méthode est que pendant toute la durée du traitement, l'IHM est verrouillée, et il est impossible d'interrompre quoi que ce soit.

 

Approche 2 : on bricole...

 

Pour permettre à l'utilisateur d'interragir pendant le traitement, on teste régulièrement les évènements d'annulation.

 

private void MaMethode() 
{
this.EffectuerTache1();
this.MettreAJour(champ1, message1, valeur1);
this.Refresh();
Application.DoEvents();
this.TesterAnnulation();

this.EffectuerTache2();
this.MettreAJour(champ2, message2, valeur2);
this.Refresh();
Application.DoEvents();
this.TesterAnnulation();

this.EffectuerTache3();
this.MettreAJour(champ3, message3, valeur3);
this.Refresh();
Application.DoEvents();
this.TesterAnnulation();

...
}

 

L'IHM n'est ici bloquée que pendant les traitements "unitaires" et l'utilisateur peut donc annuler.
L'aspect visuel sera moyen, dans la mesure ou l'écran restera figé entre chaque appel à DoEvents().
Il faut donc cliquer sur les boutons (une seule fois) sans que le clic soit pris en compte immédiatement...

 

Approche 3 : On effectue le traitement dans un Thread

 

L'objectif est ici de conserver le thread principal à disposition de l'utilisateur, et d'effectuer les traitements dans un autre thread.

 

private void MaMethode() 
{
ThreadStart methodeTraitement = new ThreadStart(this.EffectuerTraitement);
Thread traitement = new Thread(methodeTraitement);
traitement.Start();
}


private void EffectuerTraitement()
{
this.EffectuerTache1();
this.MettreAJour(champ1, message1, valeur1);
this.Refresh();
this.TesterAnnulation();

this.EffectuerTache2();
this.MettreAJour(champ2, message2, valeur2);
this.Refresh();
this.TesterAnnulation();

this.EffectuerTache3();
this.MettreAJour(champ3, message3, valeur3);
this.Refresh();
this.TesterAnnulation();

...
}

 

L'IHM est tout à fait disponible, on peut minimiser la fenêtre, la déplacer, cliquer sur le bouton d'annulation, en temps réel.
L'annulation n'est prise en compte qu'à la fin d'une tâche élémentaire.
On a donc le résultat escompté, sauf que ça compile, mais ça ne marche pas...

 

Approche 4 : On corrige notre gros bug

 

Selon les versions du Framework, les résultats obtenus vont varier.
Dans le Framework 1.x, ça va marcher dans 95% des cas, et figer l'application (complètement) de temps en temps !
Dans le Framework 2.0 on obtiendra systématiquement une exception (ce qui est quand même plus propre).

 

Ce problème est du à l'interdiction (car trop risqué) de mettre à jour un Control créé dans un thread depuis un autre thread.

 

Pour corriger ce problème, on va utiliser la méthode Control.Invoke(Delegate target, object[] args).

 

// On déclare le delegate que l´on souhaite en fonction des champs à mettre à jour
public void Delegate MettreAJourHandler(Control champ, string message, int valeur);

private void MaMethode()
{
   ThreadStart methodeTraitement = new ThreadStart(this.EffectuerTraitement);
   Thread traitement = new Thread(methodeTraitement);
   traitement.Start();
}

private void EffectuerTraitement()
{
   this.EffectuerTache1();
   this.RaffraichirDepuisThread(champ1, message1, valeur1);
   this.TesterAnnulation();
   
   this.EffectuerTache2();
   this.RaffraichirDepuisThread(champ2, message2, valeur2);
   this.TesterAnnulation();
   
   this.EffectuerTache3();
   this.RaffraichirDepuisThread(champ3, message3, valeur3);
   this.TesterAnnulation();

   ...   
}

private void RaffraichirDepuisThread(Control champ, string message, int valeur)
{
   // On regroupe les arguments dans un tableau
   object[] args = new object[3];
   args[0] = champ;
   args[1] = message;
   args[2] = valeur;
   
   // On appelle la méthode dans le Thread Principal
   base.Invoke(new MettreAJourHandler(this.MettreAJour), args);
}

private void MettreAJour(Control champ, string message, int valeur)
{
   // Le contenu de cette méthode est volontairement non compilatoire
   // A vous de déterminer quelle signature vous voulez utiliser pour mettre à jour vos contrôles
   champ.Text = message;
   champ.Value = valeur;
this.Refresh();
}

Remarques

Avec l'arrivée du Framework 2.0 et du mot clé params, une nouvelle syntaxe rend l'ensemble plus lisible.

La méthode exposée est désormais :

Control.Invoke(Delegate methode, params object[] args)

Ce qui signifie qu'il n'est plus nécessaire de regrouper les différents paramètres dans un tableau, mais qu'on peut se contenter des les énumérer.
La méthode de mise à jour depuis le thread devient donc :

 

private void RaffraichirDepuisThread(Control champ, string message, int valeur) 
{
// On appelle la méthode dans le Thread Principal
base.Invoke(new MettreAJourHandler(this.MettreAJour), champ, message, valeur);
}

 

On peut donc supprimer cette sous-méthode, et appeler le Invoke directement :

 

private void EffectuerTraitement() 
{
MettreAJourHandler methode = new MettreAJourHandler(this.MettreAJour);

this.EffectuerTache1();
base.Invoke(methode, champ1, message1, valeur1);
this.TesterAnnulation();

this.EffectuerTache2();
base.Invoke(methode, champ2, message2, valeur2);
this.TesterAnnulation();

this.EffectuerTache3();
base.Invoke(methode, champ3, message3, valeur3);
this.TesterAnnulation();

...
}

 

En .NET Compact Framework, il n'est malheureusement possible de passer des arguments qu'à partir de la version 2.0. Dans la version 1.x, la signature ne prend pas de deuxième paramètre de type object[].


 

Publié le  27/02/2008
Auteur:  Patrick A

 

Commentaires

Pas de commentaires

Si vous souhaitez ajouter un commentaire vous devez être authentifié.

 

ASP MAGAZINE  ASP-PHP.NET  C²I  CodePPC  CodeS-SourceS  Dotnet-News.com  Tech Head Brothers 

Dotnet-Project.com© tous droits réservés
Webmaster Aleks. Ont collaboré à l'aboutissement de ce projet :
CodeS-SourceS.com, ASP-PHP.Net, DotNet-FR.org, C2i.fr, Newsletter ASP.NET.