IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

[best practices][thread] retrouver l'UI


Sujet :

C#

  1. #1
    Membre actif
    Inscrit en
    Septembre 2003
    Messages
    391
    Détails du profil
    Informations forums :
    Inscription : Septembre 2003
    Messages : 391
    Points : 207
    Points
    207
    Par défaut [best practices][thread] retrouver l'UI
    Bonjour,

    Je recherche les meilleures pratiques sur la question du multi-thread avec form.
    J'ai déjà lu plei nde tuto, mais ils traitent toujours de cas simples, tout dans la même classe, etc.
    j'ai une classe qui va se charger de tâches en background qui ne sera pas Form1.
    je cherche a mettre en place le systeme le plus propre possible pour depuis cette classe appeller une methode de Form1 (en "delegate")
    exemple,
    soit la classe Form1 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        public partial class Form1 : Form
        {
            background worker;
            public Form1()
            {
                InitializeComponent();
                worker = new background();
            }
     
            private void button1_Click(object sender, EventArgs e)
            {   
                worker.setup(par1.Text, par2.Text);             // prépare les element pour le thread
     
                // lance le nouveau thread.
                Thread thread = new Thread(new ThreadStart(worker.start));
                thread.IsBackground=true;
                thread.Start();
            }
            public void display(string info) // met à jour l'UI
            {
                Label1.Text=info;
            }
    et ma classe background :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     public class background
        {
            public readonly object verrou = new object();
     
            delegate void strParamDelegate(string info);
     
            private string p1, p2; // parametres pour le thread worker.
     
            public void setup(string par1, string par2) // appel unique (avant de lancer le thread)
            {
                p1 = par1;
                p2 = par2;
            }
     
            public void start() // thread "worker"
            {
                updateui(""); //par exemple
                Thread.Sleep(1000);
            }
     
            // met à jour l'interface utilisateur
            private void updateui(string info)
            {
                BeginInvoke(new strParamDelegate(XXXX.display), new object[]{info}); //delegate asynchrone
            }
        }
    Voilà, mon probleme est "par quoi je remplace XXXX ?"
    je pourrais mettre la methode display en static (mais c'est trés crade)
    je pourrais utiliser une variable globale faisant réference à ma form instanciée, mais bof
    je pourrai passer une ref de l'instance du form instancié dans la classe background par l'intermediare de la methode setup()...

    Bref, j'aimerai bien avoir qqchose de propre, et séparer la classe form de la classe de traitement...

    Merci pour vos conseils.

  2. #2
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    bonjour

    pourquoi ne pas passer un delegate à ta classe background et ce delegate sera ainsi un delegate de gestion de la données et pourra provenir
    de chez n'importe qui ?
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  3. #3
    Membre actif
    Inscrit en
    Septembre 2003
    Messages
    391
    Détails du profil
    Informations forums :
    Inscription : Septembre 2003
    Messages : 391
    Points : 207
    Points
    207
    Par défaut
    Citation Envoyé par theMonz31 Voir le message
    pourquoi ne pas passer un delegate à ta classe background ?
    T'as 3 ou 4 lignes de code pour que je voie le concept ?
    Merci.

  4. #4
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    En gros tu remplaces la méthode "updateui" de "background" par un champ "updateui" de type Action qui sera fourni via le constructeur de "background".


    Cela dit tu devrais te renseigner sur async/await, histoire d'avoir un code du type (sur l'UI) :
    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    async void OnButtonClick(....)
    {
       this.Text = "Veuillez patienter";                              // On commence sur le thread UI
       await Task.Run(OpérationEnArrièrePlan);              // La méthode OnButtonClick se termine à ce moment pendant que OpérationEnArrièrePlan se poursuit sur un autre thread.
       this.Text = "Fini";                                                // Une fois OpérationEnArrièrePlan terminée la fin de OnButtonClick est invoquée sur le thread UI.
    }

  5. #5
    Membre actif
    Inscrit en
    Septembre 2003
    Messages
    391
    Détails du profil
    Informations forums :
    Inscription : Septembre 2003
    Messages : 391
    Points : 207
    Points
    207
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    En gros tu remplaces la méthode "updateui" de "background" par un champ "updateui" de type Action qui sera fourni via le constructeur de "background".
    Je ne connais pas trés bien (je viens plutot du C++) mais j'ai essayé ceci :
    dans ma classe form :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            private void button1_Click(object sender, EventArgs e)
            {   
                // prépare les element pour le thread
                worker.setup(par1.Text, par2.Text);
                worker.register(display);
    et dans la classe background :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            private Action<string> updater;
            public void register(Action<string> dlg) // enregistre un delegate pour la maj de l'UI
            {
                updater = dlg;
            }
    et du coups je l'apelle ainsi (depuis la classe background) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            private void updateui(string info)
            {
    //            BeginInvoke(new strParamDelegate(???.display), new object[]{info));
                updater(info);
            }
    Mais du coups, il y a un truc que j'ai pas bien fait, car j'ai une exception "invalidOperation", comme si j'avais appellé form.display() directement ! pourtant un "Action" est un delegate non ?


    Je ne suis pas contre un ptit coup de main supplementaire pour faire marcher ce code


    Sinon,
    Citation Envoyé par DonQuiche Voir le message
    Cela dit tu devrais te renseigner sur async/await
    Alors ca je ne connais pas du tout,
    c'est peut être parce que je cible du .NET 3.5 je viens de voir que c'est assez recent ce truc...

    Merci

  6. #6
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par hpfx Voir le message
    Mais du coups, il y a un truc que j'ai pas bien fait, car j'ai une exception "invalidOperation", comme si j'avais appellé form.display() directement ! pourtant un "Action" est un delegate non ?

    Néanmoins, j'ai quand même une question : là on pert l'aspect asynchrone que l'on avait avec le BeginInvoke non ? bon, c'est pas bien grace en soit...

    Je ne suis pas contre un ptit coup de main supplementaire pour faire marcher ce code
    En fait c'est désormais ta méthode display qui doit s'occuper d'invoquer BeginInvoke. En somme celui qui instancie "background" prend aussi la responsabilité de gérer la synchronisation à la fin, via BeginInvoke, et encapsule tout cela dans le délégué fournit au constructeur de background.

    Donc ton code est bon à part ce petit détail. Côté propreté du code je te conseille de créer un "displayAsync" qui s'occupera d'invoquer "display" via BeginInvoke. Et tu passeras "displayAsync" à "background" au lieu de "display". Comme ça "displayAsync" ne s'occupe que de la plomberie de synchronisation et "display" uniquement de la plomberie UI.


    alors ca je ne connais pas du tout,
    c'est peut être parce que je cible du .NET 3.5 je viens de voir que c'est assez recent ce truc...
    En effet. Et c'est bien dommage car c'est vraiment formidable pour faire des aller-retours entre l'UI et d'autres threads. Ca évite beaucoup de plomberie comme tu as pu le voir.

  7. #7
    Membre actif
    Inscrit en
    Septembre 2003
    Messages
    391
    Détails du profil
    Informations forums :
    Inscription : Septembre 2003
    Messages : 391
    Points : 207
    Points
    207
    Par défaut
    Bonjour,
    Vraiment désolé pour cette réponse tardive, j'ai eu un problème de santé...

    Citation Envoyé par DonQuiche Voir le message
    En fait c'est désormais ta méthode display qui doit s'occuper d'invoquer BeginInvoke.
    Ha oui, c'est mieux maintenant...

    Bon, du coups, j'ai fait le invoke dans display. j'ai essayé de ne faire qu'une seule methode display (qui puisse faire un invoke sur elle même si besoin, j'ai vu cette pratique par ailleurs, je voulais tester)
    alors ça donne ca :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
            delegate void StringParameterDelegate(string value);
            public void display(string info) // thread-safe
            {
                if (InvokeRequired)
                {
                    BeginInvoke(new StringParameterDelegate(display),new object[] { info });
                }
                else
                {
                    Label1.Text=info;
                }
            }
    ça me semble pas mal, mais je me dit que j'ai du faire un mixte des differents conseils et m'embrouiller un peu car en l'état j'ai 2 déclarations de delegates :
    • un dans la classe Form1 (delegate void StringParameterDelegate(string value))
    • un dans le classe background (Action<string> updater)


    Et j'ai deux appels "*Invoke", dans l'ordre :
    • un dans le classe background (updateui() fait un updater.Invoke...)
    • un dans la classe Form1 (display() fait un BeginInvoke...)


    Je me dit que ça ne semble pas optimal, il doit y en avoir en trop...
    voilà comment ça marche actuellement :
    • Dans Form1, j'instencie un objet de type background : worker.
    • Puis je lui passe la methode qui sera en charge de l'affichage (grace au conseils de theMonz31 et DonQuiche même si je ne le passe pas par le constructeur mais par une methode : register)
    • Je lance le nouveau thread
    • Quand ce nouveau thread a besoin de mettre à jour l'affichage, j'appelle updateui()
    • updateui() "invoke" la methode display du form1 : display(), celle qui à été passé dans la deuxieme etape, par register.
    • display() "invoke" la methode display() du thread de l'interface pour faire la mise à jour

    Bon, je me demande si c'est possible de n'avoir qu'un seul invoke (sans faire un truc degeux),
    Et que si on doit garder les deux invoke, s'il est possible de mutualiser les declarations de types delegate, je veux dire n'avoir qu'un seul "Action<string> updater;" ou "delegate void StringParameterDelegate(string value);", parce que ces deux déclarations correspondent à la même chose finalement, c'est balot.

    Si mes explications ne sont pas clair, je met le code :
    form1 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
        public partial class Form1 : Form
        {
            background worker;
            public Form1()
            {
                InitializeComponent();
                worker = new background();
            }
     
            private void button1_Click(object sender, EventArgs e)
            {   
                worker.setup(par1.Text, par2.Text);            // prépare les elements pour le thread
                worker.register(display);            // la methode qui sera en charge de la maj
     
                // lance un nouveau thread.
                Thread thread = new Thread(new ThreadStart(worker.start));
                thread.IsBackground=true;
                thread.Start();
            }
     
            delegate void StringParameterDelegate(string value);
            public void display(string info) // thread-safe
            {
                if (InvokeRequired)
                {
                    BeginInvoke(new StringParameterDelegate(display), new object[] { info });
                }
                else
                {
                    Label1.Text=info;
                }
            }
        }
    et la classe background :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
        public class background
        {
            public readonly object verrou = new object();
     
            private string p1, p2; // parametres pour le thread
            public void setup(string par1, string par2) // appel unique (avant de lancer le thread)
            {
                p1 = par1;
                p2 = par2;
            }
     
            Action<string> updater;
            public void register(Action<string> dlg) // enregistre un delegate pour la maj de l'UI
            {
                updater = dlg;
            }
     
            public void start() // thread "worker"
            {
                updateui("xxxxxxxxxxxxxxx"); //exemple
                Thread.Sleep(1000);
            }
     
            // met à jour l'interface utilisateur
            private void updateui(string info)
            {
                updater.Invoke(info);
            }
        }
    Voilà,
    Merci de votre aide.

Discussions similaires

  1. Réponses: 6
    Dernier message: 08/06/2015, 15h58
  2. Réponses: 4
    Dernier message: 23/05/2006, 15h22
  3. [Thread] Retrouver un thread dont on a plus la référence
    Par ptitjack dans le forum Concurrence et multi-thread
    Réponses: 4
    Dernier message: 01/10/2004, 11h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo