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.
You have 3 ways to do it:
make computation in the button callback and use gtk_event_pending()
/gtk_main_iteration()
use g_idle_add()
or others, and gtk_event_pending()
/gtk_main_iteration()
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.
What the documentation says is that you can still run your worker function in a thread, you just can't use GTK and GDK functions from that thread. So, you can still start the thread when you click start. But instead of updating GUI elements from the thread, you have to schedule them to be updated from the main thread by using gdk_threads_add_idle()
.
So your diagram should look something like this:
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...
If this is too complicated for your use case, and you have a computation in your worker thread that returns control to your worker thread often enough (say, you are calculating something in a loop), then you can run the calculation entirely in the main thread without locking up the GUI by briefly returning control to the GUI main loop, like so:
for (lots of items) {
result = do_short_calculation_on(one_item);
update_gui(result);
while (gtk_events_pending())
gtk_main_iteration();
}
I get this running error when i close the main window: Gtk-CRITICAL
**: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed
I think i have found a solution, using two global variables that indicate to the callback to stop and to call gtk_main_quit()
, and having trapped the "destroy" signal for the main window into a self defined callback named gtk_main_quit2()
in the following example:
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();
}
If you still have some gtk warnings after having clicked the close button, you can try to trap the "delete-event" signal instead of the "destroy" signal on the main window.