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

GTK+ avec C & C++ Discussion :

Problème de threads.


Sujet :

GTK+ avec C & C++

  1. #1
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2012
    Messages
    56
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2012
    Messages : 56
    Par défaut Problème de threads.
    Bonjour,

    J'ai un thread chargé d'insérer des lignes dans un treeview et de faire progresser une progressbar. J'essaye de migrer vers gtk 3, où les fonctions gdk_threads_enter() et gdk_threads_leave() sont dépréciées, j'aimerais adapter le code.

    Seulement voilà, sans l'utilisation de ces fonctions, l'interface est instable et plante. J'ai essayé de lancer le thread avec g_idle_add(), g_main_context_invoke() et gdk_threads_add_idle(), mais ces fonctions bloquent l'interface.

    Je ne peux pas me passer de thread, car sinon le callback bloque l'interface.

    Voici un programme de test, qui modifie le label d'un bouton avec un thread, un deuxième click sur le bouton permet d’arrêter le thread, le programme plante quand on essaye de redimensionner la fenêtre:
    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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    #include <gtk/gtk.h>
    #include <gdk/gdk.h>
     
    #include <stdio.h>
     
    int annuler = 0;
    int stop = 0;
     
    gpointer threadwhile(gpointer data) {
      GtkButton * b = (GtkButton*) data;
      int count = 0;
      char buffer[100];
     
        while(1) {
          if(stop == 1) break;
          printf("\n test %d",count);
          sprintf(buffer,"test %d",count);
          gdk_threads_enter();
          gtk_button_set_label(b,buffer);
          gdk_threads_leave();
          count ++;
        }
        annuler = 0;
        stop = 0;
        return FALSE;
    }
     
    void on_button_clicked(GtkButton * button, gpointer data) {
     
      if(annuler == 1) stop = 1;
      else {
        g_thread_new(NULL,threadwhile,button);
        annuler = 1;
      }
    }
     
     
    GtkWidget * create_window1() {
      GtkWidget * window1 = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW (window1), "test gtk3");
     
      GtkWidget * vbox12 = gtk_vbox_new (FALSE, 0);
      gtk_widget_show (vbox12);
      gtk_container_add (GTK_CONTAINER (window1), vbox12);
     
      GtkWidget * button = gtk_button_new();
      gtk_button_set_label(GTK_BUTTON(button),"compile");
      g_signal_connect ((gpointer) button, "clicked",
                        G_CALLBACK (on_button_clicked),
                        NULL);
      gtk_widget_show (button);
      gtk_container_add (GTK_CONTAINER (vbox12), button);
     
      return window1;
    }
     
     
    int main(int argc, char *argv[]) {
     
      gdk_threads_init ();
      gdk_threads_enter ();
     
      gtk_init (&argc, &argv);
     
      GtkWidget * window1 = create_window1 ();
      gtk_widget_show (window1);
      g_signal_connect ((gpointer) window1, "destroy", G_CALLBACK(gtk_main_quit), NULL);
     
      gtk_main ();
      gdk_threads_leave();
    }

  2. #2
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2012
    Messages
    56
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2012
    Messages : 56
    Par défaut
    Le problème semble résolu. En utilisant g_idle_add() et en faisant un appel à g_main_context_iteration(NULL,TRUE) ou gtk_main_iteration() juste après la modification du label du bouton.

  3. #3
    Expert confirmé
    Avatar de gerald3d
    Homme Profil pro
    Conducteur de train
    Inscrit en
    Février 2008
    Messages
    2 308
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Conducteur de train
    Secteur : Transports

    Informations forums :
    Inscription : Février 2008
    Messages : 2 308
    Billets dans le blog
    5
    Par défaut
    Sans trop regarder ton code utiliser les threads pour modifier l'interface posent effectivement de gros problèmes, voir des plantages plus ou moins aléatoires.

    En règle générale on utilise plutôt g_timeout_add(); et ses variantes. Elle permet d'insérer une fonction dans la boucle principale de Gtk+ et ainsi lors de son traitement elle ne bloque pas l'interface.

  4. #4
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2012
    Messages
    56
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2012
    Messages : 56
    Par défaut
    Merci.

    Mais même avec g_timeout_add, je dois utiliser la fonction gtk_main_iteration(), et ça ne fonctionne que si j'appelle cette fonction dans la boucle. Si je l'appelle avant, alors l'interface reste bloquée.

    Comparé à l'utilisation des fonction gdk_thread_enter() et gdk_thread_leave(), le gtk_main_iteration() est beaucoup plus lent.

    EDIT:
    Plus précisément, quand l'interface n'a pas été modifiée par le programme, je dois utiliser la fonction gtk_main_iteration_do(FALSE). Sinon le programme reste bloqué en attendant un évènement venant de l'interface graphique.

  5. #5
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2011
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Septembre 2011
    Messages : 7
    Par défaut Comment dégeler mes relations avec Gtk3 ?
    Bonjour,

    Je suis en train de porter une application C++ de Gtk2 à Gtk3 (gtkmm donc), et je suis confronté au même type de problème, mais je pense pouvoir recréer un fonctionnement à la thread_enter/thread_leave, mais pour ça, je dois passer par gdk_threads_add_idle. Le problème, c'est que mon application ne démarre plus, elle gèle avant même d'ouvrir la première fenêtre! J'avais testé d'autres solutions qui permettais au moins d'ouvrir la fenêtre principale, avec tout ses widgets, mais cela gelais également, et parfois plantait. En fait, je ne suis pas sûr que le gèle vienne de là, car on utilise aussi gdk_threads_add_idle ici et là dans le code, mais s'il y a un problème avec cette fonction (un bug dans Glib ?), j'appliquerais les solutions partout où il le faudra.

    bertrand125, pourrais-tu élaborer ton dernier commentaire? Comment savoir d'ailleurs s'il faut utiliser gtk_main_iteration() ou gtk_main_iteration_do(FALSE) ?

    Voilà le bout de code qui selon moi et selon la doc devrait fonctionner, si vous avez une idée de pourquoi ça gèle...!? Et est-ce crado (càd lent) comme solution? Je précise que la dépréciation de thread_enter/thread_leave est une grosse perte pour nous, car certains worker thread accèdent à la GUI en écriture puis en lecture un peu plus loin dans la même fonction. C'est une grosse galère de tout passer en asynchrone, thread_enter/thread_leave permettait de tout faire en synchrone, quitte à attendre que la GUI soit dispo, ce qui n'était pas un problème. C'est d'ailleurs ce que fait la classe GThreadLock ci-dessous. On la met au début du bloc de code à protéger, et le tour est joué!

    Code Fichier .h : 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
     
    class GThreadMutex
    {
    public:
    	Glib::Threads::Mutex GUI;
    	Glib::Threads::Cond GUICond;
     
    	Glib::Threads::Mutex myCode;
    	Glib::Threads::Cond myCodeCond;
    };
     
     
    class GThreadLock : public GThreadMutex
    {
    public:
    	GThreadLock();
    	~GThreadLock();
    };

    Code Ficher .cc : 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
     
    // execution dans le thread A (GUI)
    gboolean giveMeAGo(void* data) {
        GThreadMutex *threadMutex = static_cast<GThreadMutex*>(data);
     
        Glib::Threads::Mutex::Lock lock(threadMutex->GUI);
        threadMutex->myCodeCond.signal();   // execution du code protege dans le thread B
        threadMutex->GUICond.wait(threadMutex->GUI);  // attend la fin de l'éxecution du code dans le thread B
        return false;
    }
     
    // instanciation dans le thread B
    GThreadLock::GThreadLock() {
        Glib::Threads::Mutex::Lock lock(myCode);
        gdk_threads_add_idle(giveMeAGo, this);
        myCodeCond.wait(myCode);
    }
     
    GThreadLock::~GThreadLock() {
        Glib::Threads::Mutex::Lock lock(GUI);
        GUICond.signal();
    }

  6. #6
    Membre confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juillet 2012
    Messages
    56
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2012
    Messages : 56
    Par défaut
    Edit: En fin de post, je décris une solution à base de thread qui ne nécéssite normalement pas de mutex, ni de gtk_main_iteration(), c'est celle que tu semble utiliser. Sur mon système elle fonctionne mais je n'utilise pas de mutex pour attendre que la fonction soit entièrement exécutée.

    Pour ton problème d'interface bloquée, j'ai l'impression que ça vient du wait() sur la GUICond. Tu peux peut-être essayer de faire sans ce mutex et cette condition ? Peut-être en divisant la fonction giveMeAGo() en deux fonctions, et en faisant deux appels à gdk_threads_add_idle() ?

    bertrand125, pourrais-tu élaborer ton dernier commentaire? Comment savoir d'ailleurs s'il faut utiliser gtk_main_iteration() ou gtk_main_iteration_do(FALSE) ?
    gtk_main_iteration_do(FALSE) est non bloquant, si il n'y a pas d'évènement à traiter, elle retourne immédiatement, alors que gtk_main_itération() attend qu'il y ait un évènement à traiter. J'utilise ces fonctions quand, à la place d'un thread, je fait des calculs directement dans le callback d'un bouton, ou dans une fonction ajoutée à la main loop avec g_idle_add() ou gdk_threads_add_idle(). Quand j'utilise des threads, je n'ai pas besoin de faire appels à gtk_main_iteration()/gtk_main_iteration_do().

    Plus précisément j'utilise maintenant la boucle:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(gtk_events_pending()) gtk_main_iteration();
    Tant qu'il y a des èvènement à traiter, elle appelle gtk_main_iteration() pour les traiter. Par exemple en C, j'utilise la fonction compute_func() qui sous gtk2 était un thread, mais maintenant est ajoutée à la main loop avec g_idle_add() ou gdk_threads_add_idle():
    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
    gboolean compute_func(gpointer data) {
     
      // do some computation...
     
      // modify the GUI:
      gtk_label_set_text(label,"text");
      // run the main loop to update the GUI and get it responsive:
      while(gtk_events_pending()) gtk_main_iteration();
     
      // do some other computation...
     
      // huge computation in a loop:
      while(1) {
        // do some computation...
     
        // update GUI and treat events from it:
        while(gtk_events_pending()) gtk_main_iteration();
      }
     
      return FALSE;
    }
     
    void on_button_clicked(GtkButton * button, gpointer data) {
     
        g_idle_add(compute_func,data);
    }
    Même si l'interface n'a pas été modifiée, il faut fréquemment faire appel à la boucle gtk_event_pending()/gtk_main_iteration() pour traiter les évènements venant de la GUI, comme les clicks de souris.

    On peut faire de même directement dans le callback du bouton que l'utilisateur a cliqué. Mais dans les deux cas, il y a un problème quand on quitte l'application alors qu'un calcul est en cours, gtk continue d’exécuter la fonction après avoir détruit la fenêtre et affiche plein de gtk critical warnings. J'utilise donc 2 variables globales pour vérifier si l'utilisateur a cliqué le bouton pour quitter l'application, le code suivant montre l'utilisation de gtk_event_pending() et gtk_main_iteration() dans un callback de bouton, mais on peut facilement adapter dans une fonction ajoutée à la main loop avec gdk_threads_add_idle:
    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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    int process_running = 0; // indicate if the "process" is running
    int stopprocess = 0; // indicate to the callback to stop or not
     
    void gtk_main_quit2(GtkWidget * window, gpointer data) {
      if(process_running == 0) gtk_main_quit(); // if the "process" isn't running
                                                // then quit
     
      stopprocess = 1; // indicate to the button callback to stop and quit
    }
     
    void on_button_clicked(GtkButton * button, gpointer data) {
     
      // indicate the "process" is running:
      process_running = 1;
     
      // do some computation...
     
     
      while(gtk_events_pending()) gtk_main_iteration();
      if(stopprocess == 1) {
        // if close button clicked then quit:
        gtk_main_quit();
        return;
      }
     
      // do some other computation...
     
      // huge computation in a loop:
      while(1) {
        // do some computation...
     
     
        while(gtk_events_pending()) gtk_main_iteration();
        if(stopprocess == 1) {
          // if close button clicked then quit:
          gtk_main_quit();
          return;
        }
      }
     
      while(gtk_events_pending()) gtk_main_iteration();
      // indicate the "process" is finished:
      process_running = 0;
      // in the case the user clicked close button just at the end of computation:
      if(stopprocess == 1) {
        gtk_main_quit();
        return;
      }
    }
     
    int main() {
     
      gtk_init();
      Gtkwidget * window = create_window();
      g_signal_connect ((gpointer) window, "destroy", G_CALLBACK(gtk_main_quit2), NULL);
      gtk_main();
     
    }
    Une solution à base de thread sans utiliser de mutex est normalement possible, on lance un thread et à chaque fois que le thread veut modifier l'interface graphique, il fait appel à g_idle_add() ou gdk_threads_add_idle() pour ajouter une fonction à la gtk main loop, et c'est cette fonction qui sera chargée de modifier ou de lire l'interface:
    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
    Main thread     Worker thread
        |
    Button clicked
        |      \________
        |               \
        |           Start worker function
        |                |
        |           Computation
        |                |
        |           Want to update GUI
        |                |
        |           gdk_threads_add_idle(function1, data1)
        | ______________/|
        |/               |
        v           More computation
    function1 runs       |
        |           Want to update GUI
    GUI updated          |
        |           gdk_threads_add_idle(function2, data2)
        | ______________/|
        |/               |
        v           More computation
    function2 runs       |
        |      
      etc...
    Ce qui donne en C:
    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
    gboolean update_gui(gpointer data) {
     
      // update the GUI:
      gtk_button_set_label(button,"label");
      gchar * text = gtk_entry_get_text(GTK_ENTRY(entry));
     
     
      return FALSE;
    }
     
    gpointer threadcompute(gpointer data) {
      int count = 0;
     
      while(count <= 10000) {
        printf("\ntest %d",count);
        // sometimes update the GUI:
        gdk_threads_add_idle(update_gui,data_gui);
        // or:
        g_idle_add(update_gui,data_gui);
     
        count++;
      }
     
      return NULL;
    }
     
    void on_button_clicked(GtkButton * button, gpointer data) {
     
        g_thread_new("thread",threadcompute,data);
    }
    Les données pour modifier l'interface doivent être transmises à la fonction update_gui() sous forme de pointeur vers une structure ou un objet, nommé "data_gui" dans l'exemple, lors de l'appel à gdk_threads_add_idle(update_gui,data_gui);

    Sinon, avec une ancienne version de mingw, j'avais besoin de verrouiller les accès à l'interface avec un mutex, dans la fonction update_gui() de l'exemple précédent, sinon l'interface était instable et plantait.

  7. #7
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2011
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Septembre 2011
    Messages : 7
    Par défaut
    Merci pour cette réponse détaillée, mais cette solution est la solution officielle... qui ne me convient pas, car elle nous oblige a gérer la GUI de manière asynchrone. Or ma fonction met à jour l'interface dans une partie du code, et peut la lire tout de suite après dans une autre fonction. Il me faut travailler de manière synchrone, quitte à attendre que le thread principal soit disponible. La nouvelle solution officielle ne le permet plus, et cela me ferait revoir pas mal de chose dans le code!

    Suite à ta réponse, j'ai quand même creusé la piste du Mutex/Condition, et je pense être parvenu à un truc satisfaisant, même si je ne peux pas encore le tester de manière intensif (le premier test a tout de même été concluant), je vous la livre pour référence, au cas où cela intéresserait certains, mais elle reste "à vérifier":

    Code .h : 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
     
    /** 
     * @brief Lock GTK for critical section.
     *
     * Will unlock on destruction. To use:
     *
     *   <code>
     *     {
     *       GThreadLock lock;
     *       // critical code
     *     }
     *   </code>
     *
     *   This is a replacement for the former gdk_threads_enter / gdk_threads_leave pair that has been deprecated.
     *   It does the same, but there may be a speed penalty, hopefully negligible.
     */
     
    class GThreadLock
    {
    public:
    	Glib::Threads::Mutex operation;
    	Glib::Threads::Cond operationCond;
    	Glib::Threads::Mutex GUI;
    	Glib::Threads::Cond GUICond;
    	bool sameThread;
     
    	GThreadLock();
    	~GThreadLock();
    };
    Code .cc : 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
     
    gboolean giveMeAGo(void* data) {
        GThreadLock *threadMutex = static_cast<GThreadLock*>(data);
        Glib::Threads::Mutex::Lock lock2(threadMutex->GUI);
        {
        Glib::Threads::Mutex::Lock lock(threadMutex->operation);
        threadMutex->operationCond.signal();
        }
        threadMutex->GUICond.wait(threadMutex->GUI);
     
        return false;
    }
     
    GThreadLock::GThreadLock() : sameThread(false) {
        if (Glib::Threads::Thread::self() == mainThread) {
            sameThread = true;
            return;
        }
     
        Glib::Threads::Mutex::Lock lock(operation);
     
        gdk_threads_add_idle(giveMeAGo, this);
     
        operationCond.wait(operation);
    }
     
    GThreadLock::~GThreadLock() {
        if (!sameThread) {
            Glib::Threads::Mutex::Lock lock(GUI);
            GUICond.signal();
        }
    }

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

Discussions similaires

  1. Problème de thread : Plus de ressources système
    Par OliverSleep dans le forum C++Builder
    Réponses: 17
    Dernier message: 07/02/2006, 15h35
  2. [VB.NET] Problème de Thread
    Par Sadneth dans le forum ASP.NET
    Réponses: 26
    Dernier message: 31/01/2006, 10h12
  3. Problème synchronisation threads
    Par Linio dans le forum Concurrence et multi-thread
    Réponses: 19
    Dernier message: 11/01/2006, 16h57
  4. [MFC] Problème de Threads + Timers
    Par Invité dans le forum MFC
    Réponses: 8
    Dernier message: 30/11/2005, 10h51
  5. [VC++6][DX9] Problème de thread lors d'un blit ...
    Par grandjouff dans le forum DirectX
    Réponses: 2
    Dernier message: 12/06/2003, 22h22

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