Search code examples
cmultithreadinggtk3gdk

GTK3 and multithreading, replacing deprecated functions


I would like to replace deprecated functions gdk_threads_enter()/leave() in my application that uses threads. The application as it is now, works perfect (although i am not sure if this is the right way to do it).

My main loop, runs the gtk_main and the signal handlers. When i receive a start button, i start a thread, that runs in the background along the main. How can i update the GUI from that thread. I know per the Documentation of GTK3 and GDK3, they say avoid it by using

gdk_threads_add_idle() 

or

gdk_threads_add_timeout() 

But how do I do this if I want the updating to be done only when I click start? is there any example. I am not asking how to use gdk_threads_add_idle(), I am asking how to run worker function in the main without a thread after clicking start.

Button clicked --> start the worker function "in thread previously" --> update large amount of GUI elements in the GUI window.


Solution

  • You have 3 ways to do it:

    1. make computation in the button callback and use gtk_event_pending()/gtk_main_iteration()

    2. use g_idle_add() or others, and gtk_event_pending()/gtk_main_iteration()

    3. use a thread, eventually a mutex, and g_idle_add() or others. Normally, a mutex isn't needed but it may solve some bugs or Heisenbugs.

    The third solution seems to be the best, because with the first two methods, I experienced some problems when exiting the application while a computation was running. The application didn't exit and was printing a lot of "Gtk Critical" warnings. (I tried it on Windows and mingw32).


    1. button callback:

    If you want to run the worker thread in the main gtk loop, you can directly make the computation in the button callback, updating the GUI and treating events from it with gtk_event_pending() and gtk_main_iteration(), as in the following sample code:

    void on_button_clicked(GtkButton * button, gpointer data) {
    
      // do some computation...
    
      // modify the GUI:
      gtk_label_set_text(label,"text");
    
      // run the main iteration to update the GUI,
      // you need to call these functions even if the GUI wasn't modified,
      // in order to get it responsive and treat events from it:
      while(gtk_events_pending()) gtk_main_iteration();
    
      // do some other computation...
    
      // huge computation in a loop:
      while(1) {
        // do some computation...
    
        // update the GUI and treat events from it:
        while(gtk_events_pending()) gtk_main_iteration();
      }
    }
    

    2. g_idle_add():

    You can also use, instead of g_thread_new(), gdk_thread_add_idle()(in the case some libraries not under your control may use gdk_threads_enter()/leave()) or g_idle_add() or g_main_context_invoke():

    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);
    }
    

    3. thread and mutex:

    In some cases using a thread make the computation to be faster, so when using a worker thread NOT in the main gtk loop, and when updating the GUI in function added to the main loop with gdk_threads_add_idle() or g_idle_add() from the worker thread, you may have to lock the access to the GUI using a mutex, because there may be a conflict between the functions accessing the GUI. The mutex have to be initialized with g_mutex_init(&mutex_interface); before beeing used by the application. For example:

    GMutex mutex_interface;
    
    gboolean update_gui(gpointer data) {
      g_mutex_lock(&mutex_interface);
      // update the GUI here:
      gtk_button_set_label(button,"label");
      // And read the GUI also here, before the mutex to be unlocked:
      gchar * text = gtk_entry_get_text(GTK_ENTRY(entry));
      g_mutex_unlock(&mutex_interface);
    
      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);
        // or:
        g_idle_add(update_gui,data);
    
        count++;
      }
    
      return NULL;
    }
    
    void on_button_clicked(GtkButton * button, gpointer data) {
    
        g_thread_new("thread",threadcompute,data);
    }
    

    If you need the functions updating the GUI to be executed in a specific order, you need to add two counters and to assign a number to each function called with g_idle_add() or gdk_threads_add_ilde():

    GMutex mutex_interface;
    
    typedef struct _data DATA;
    struct _data {
      gchar label[1000];
      GtkWidget * w;
      int num;
    };
    
    
    int counter = 0;
    int counter2 = 0;
    
    gboolean update_gui(gpointer data) {
      DATA * d = (DATA *)data;
    
      debutloop:
      g_mutex_lock(&mutex_interface);
      if(d->num != counter2) {
        g_mutex_unlock(&mutex_interface);
        goto debutloop;
      }
      counter2++;
      // update the GUI here:
      gtk_button_set_label(GTK_BUTTON(d->w),d->label);
      // And read the GUI also here, before the mutex to be unlocked:
      gchar * text = gtk_entry_get_text(GTK_ENTRY(entry));
      g_mutex_unlock(&mutex_interface);
    
      free(d);
    
      return FALSE;
    }
    
    gpointer threadcompute(gpointer data) {
      int count = 0;
    
      while(count <= 10000) {
        printf("\ntest %d",count);
    
        DATA * d = (DATA*)malloc(sizeof(DATA));
        sprintf(d->label,"%d",count);
        d->w = (GtkWidget*)data;
        d->num = counter;
        counter++;
        // update the GUI:
        g_idle_add(update_gui,d);
    
        count++;
      }
      return NULL;
    }
    
    void on_button_clicked(GtkButton * button, gpointer data) {
    
        g_thread_new("thread",threadcompute,button);
    }
    

    I have also tested the case of locking individual widgets instead of the whole GUI, and it seems to work.