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

Dotnet Discussion :

Multithreading et Forms figée


Sujet :

Dotnet

  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut Multithreading et Forms figée
    Bonjour,
    je suis en train de développer ma première application Client/Serveur qui consiste à balancer des alertes aux utilisateurs de l'entreprise.

    d'un coté j'ai mon serveur qui interroge une base de données, qui écoute les connexions des client et leur balance toutes les 10 secondes des messages d'alerte (s'il y en a).
    Le coté serveur fonctionne parfaitement.

    du coté client, j'ai un Winform caché (ou non, on peut l'afficher si l'on veut) dont une icône est affichée dans le systray.
    Ce client possède un thread 'TCP' qui se connecte au serveur et écoute ses messages.
    Lorsque ce thread reçoit un message, il génère un event, que le Winform client consomme.
    le Client contient un 'List<popup>', quand il reçoit l'event dont j'ai parlé précédemment, il crée un popup (winform), l'ajoute dans sa liste et l(les)'affiche(s) en bas à droite de l'écran.
    Tout cela fonctionne SAUF QUE : Le popup s'affiche mais reste figé, ne dessine pas tous ses composants, est non cliquable avec un waitcursor en permanence quand je passe la souris dessus;
    Aucune erreur n'est générée.
    Comme si son thread était complètement arrêté.
    Normalement, mon popup a un timer qui se déclenche toutes les demi-secondes pour le faire clignoter, j'ai beau mettre un point d'arret sur le callback du timer, il n'est jamais déclenché. (et pourtant quand j'utilise ces popups indépendamment ils clignotent bel et bien).

    Est-ce car ces popups sont créés par un event généré par un autre thread ?
    Dans tous les cas, quand mon programme d'alerte était monothread (le client récupérait lui même les infos de la base de données) tout fonctionnait très bien.
    Difficile de donner un bout de code vu la taille du projet.

    et j'ai beau mettre des .InvokeRequired .Invoke() partout le problème reste (de toute manière aucune erreur n'est générée)
    Si quelqu'un s'est déjà retrouvé devant un tel problème et a une idée je suis très intéressé
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  2. #2
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Je me suis amusé à faire un diagramme basique représentant le programme :
    Nom : Diagramme_Basique.png
Affichages : 148
Taille : 54,6 Ko

    je pense que c'est parce que l'Event Message_Recu est envoyé depuis un autre thread (Ecoute_Donnees) que ca fige mes popups, mais je n'ai aucune idée de comment corriger le problème.
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  3. #3
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Vu que personne n'a l'air d'avoir d'idée, j'ai décidé de simplifier mon programme au max pour pousser des tests.

    j'ai fait deux tests :

    Le plus simple fonctionne, voici le code
    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
     
    static void Main()
            {
                    Configuration();
     
                    Affiche_Popup_Bidon();
     
                    Application.Run();
            }
     
    static void Affiche_Popup_Bidon()
            {
                DataTable dt_truc = new DataTable("truc");
                string requete = "SELECT * FROM [1fo_Messages]";
                Outils_BDD.Utiles.Recup_Requete_DataTable(dt_truc, requete, _connexions["1fo_PS"], true, true); //une chtite fonction à moi qui récupère le contenu de la requete dans une datatable
                Popup_Note pn_truc = new Popup_Note(new Message(dt_truc.Rows[0]), _delaiRePopup_Rouge, _delaiRePopup_Orange, _delaiRePopup_Vert);//un popup de type 'Form' qui prend en entrée une datarow représentant un message
                pn_truc.Affiche(true);
            }
    Cette version récupère le premier message venu, me crée un popup et l'affiche; mon popup apparait bien ainsi que ses différents controls, le message est visible et il clignote (fonctionnement interne au popup dû à un timer)

    J'ai ensuite ajouté ma couche réseau dans le 'Main'. La classe Client se connecte au serveur (ici 127.0.0.1 car le serveur tourne pour le moment sur mon poste pour les tests) puis, via un thread, écoute le réseau. Quand la classe Client reçoit un message (qui n'est autre qu'un datatable, mais ici on s'en fout...) elle génère un événement auquel le Main est abonné. Quand le Main consomme cet évènement, il lance simplement la même fonction qu'avant : Affiche_Popup_Bidon().
    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
     
    static void Main()
            {
                    Configuration();
     
                    //Affiche_Popup_Bidon();
                    Outils_ClientServeur.Client monClient = new Outils_ClientServeur.Client("127.0.0.1", TaillesPackets.ko512, true);
                    monClient.MessageRecu += monClient_MessageRecu;
     
                    Application.Run();
            }
     
     private static void monClient_MessageRecu(object sender, string e)
            {
                Affiche_Popup_Bidon();
            }
    Là, le popup apparait, mais figé ! ses controls sont figés ou n'apparaissent pas, rien ne clignote...
    j'ai fait exprès d'exécuter la même méthode Affiche_Popup_Bidon() afin de bien démontrer que le popup lui-même n'est pas en cause, mais que c'est parce qu'il est affiché depuis un event généré par un autre thread.

    Quelque chose m'échappe t'il ?
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  4. #4
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Encore plus simple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    static void Main()
            {
                    System.Threading.Timer machin = new System.Threading.Timer(new System.Threading.TimerCallback(bidule),null,0,5000);
     
                    Application.Run();
            }
     
            static void bidule(object state)
            {
                Form burp = new Form();
                burp.Show();
            }
    ici, le code est censé m'afficher une simple Form vide toutes les 5 secondes.
    C'est bien le cas, mais toutes les Forms sont figées !?¿ !!!!

    Et je n'arrive pas à trouver de doc sur ce simple cas
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  5. #5
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Visiblement, dans le dernier cas corrige le problème.

    je vais me contenter de ça à défaut d'explications
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  6. #6
    Expert éminent sénior Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 154
    Points : 25 072
    Points
    25 072
    Par défaut
    déjà il test interdit de créer des forms sur un autre thread que le thread principal, et le threading.timer est sur un autre thread
    utilises plutot System.Windows.Forms.Timer et tes fenetres ne seront pas figées

    et faire des choses avant le application.run (visuelles j'entends) je reste sceptique
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  7. #7
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Salut, et déjà merci pour ta réponse;

    afficher des trucs avant le Application.Run() ne pose absolument aucun problème, tu peux tester
    d'ailleurs le Affiche_Popup_Bidon() placé avant est bien le seul test qui fonctionne.

    ensuite, pour le dernier test ou j'utilise le Timer, il était surtout pour voir d'où pouvait provenir ce freeze.
    Le but est bel et bien de simplement consommer un event (dans le thread principal) afin de créer cette Form (depuis le thread principal) sauf que l'event a été envoyé d'un autre thread... et c'est bien cela qui m’embête, je ne sais pas depuis quel thread est exécuté la fonction "monClient_MessageRecu(object sender, string e)"; logiquement elle fait partie du Main(), donc du thread principal. Mais visiblement ce n'est pas dit
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  8. #8
    Expert éminent sénior Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 154
    Points : 25 072
    Points
    25 072
    Par défaut
    quand tu es dans un event, tu peux tester invokerequired aussi pour savoir si tu es sur le bon thread (que tu dis pourtant avoir testé)
    si tu n'as pas de controle sous la main et que tu es sur d'avoir une fenetre ouverte tu peux utiliser system.windows.forms.application.openforms(0).invokerequired
    si tu n'as pas de fenetre d'ouverte, tu peux te mettre le currentthread dans une variable "publique" pour tester plus tard si tu es sur le même
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  9. #9
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Je suis dans le troisième cas.

    J'ai donc suivi ton conseil et ai créé une référence depuis le thread principal pour comparer avec le currentThread dans l'event "monClient_MessageRecu".
    Le résultat est tombé : ce n'est pas le même thread (ID différent)

    Le InvokeRequired et le Invoke() je connais bien, mais dans ce cas là (sans control), sais-tu comment demander au thread principal de traiter la demande ?

    (en tout cas déjà merci car ça m'a bien fait avancer le schmilblick )
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  10. #10
    Expert éminent sénior Avatar de Pol63
    Homme Profil pro
    .NET / SQL SERVER
    Inscrit en
    Avril 2007
    Messages
    14 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : .NET / SQL SERVER

    Informations forums :
    Inscription : Avril 2007
    Messages : 14 154
    Points : 25 072
    Points
    25 072
    Par défaut
    créer un windows forms timer, le démarrer
    quand tu recois des données (sur un autre thread donc) tu les stockes dans quelque chose (classe ou collection)
    et sur l'event tick du timer tu regardes s'il y a quelque chose de stocké, auquel cas tu le traites et tu vides (attention, les collections ne sont pas thread safe, synclock ou autre surement requis)

    ou alors garder une variable d'une instance de form ou de controle qui a déja été affiché mais qui n'est pas détruit (handlecreated mais pas disposed) pour faire invoquerequired et invoque dessus
    (solution plus immédiate, mais de peu, un timer à 100ms qui ne fait rien la plupart du temps ca ne consomme rien)
    Cours complets, tutos et autres FAQ ici : C# - VB.NET

  11. #11
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Ca confirme mon idée de boucle que j'avais en tête pour eviter le cross thread. Je testerais ca demain, merci encore
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

  12. #12
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2005
    Messages
    482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Décembre 2005
    Messages : 482
    Points : 625
    Points
    625
    Par défaut
    Récapitulatif, pour clore ce sujet :

    La méthode exécutée à la réception de l'event envoyé par le thread secondaire étant exécutée dans le contexte de ce dernier,
    je ne m'en sert que pour remplir/vider ma collection de messages (qui est thread safe).

    Dans le thread principal, un Timer (System.Windows.Form) se déclenche toutes les secondes pour lire et traiter la collection de messages.
    Le Timer (Form) déclenche son event dans le thread principal contrairement au Timer (System.Threading) qui déclenche son Event dans un autre Thread (ce que je n'avais pas tout à fait intégré :p)
    Comme l'event du timer est déclenché dans le thread principal, il peut créer des controls sans aucun problème; il faut juste protéger la collection de messages au moment ou je l'exploite pour éviter que le thread secondaire y rajoute ou y supprime des messages (auquel cas la collection génèrera une erreur car elle aura changé incognito) avec la fonction Lock(maCollection).

    Merci d'avoir résolu mon problème Pol63, mon programme est enfin terminé
    "Essayer est le premier pas vers l'Echec !" (Homer Simpson)

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. CLient IRC : La form se fige
    Par Altor dans le forum Windows Forms
    Réponses: 8
    Dernier message: 17/02/2016, 18h25
  2. Réponses: 87
    Dernier message: 06/07/2011, 15h33
  3. multithreading avec windows form
    Par tortuegenie dans le forum Windows Forms
    Réponses: 9
    Dernier message: 06/06/2011, 14h23
  4. Form qui reste figée
    Par GGerald77 dans le forum Macros et VBA Excel
    Réponses: 9
    Dernier message: 17/09/2007, 21h13
  5. Form qui se fige lors d'un traitement malgré thread
    Par SesechXP dans le forum Windows Forms
    Réponses: 7
    Dernier message: 31/05/2007, 11h46

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