Skip to:

Tiago Cogumbreiro

O Irrepupável

Back to top

Interfacing Threaded Programming with Gtk+

Version 0.1

Last updated: 25-08-04

Introduction

Threaded programming with Gtk+ turns your application more efficient and responsive. But, because the Gtk+ library is not thread safe, interfacing it with multi threaded code has to follow some simple procedures. Also in order for it to be effective, good communication with the user is essential.

This article will try to describe how to develop multithreaded applications which use the Gtk+ toolkit in an Model-View-Controller (MVC) way. It will also provide some guidelines with the respective examples on how to improve the user experience with respect to this matter.

MVC

MVC is, amongst other things, a way of designing your application in a structured way. Everything you design should work in an abstract way, independently of it's presentation. This is why whenever you create an application you should consider developing a command line interface as well as a graphical user interface. This way of thinking does not mean that you actually will implement one of them but instead decouples your application from the presentation layer. This also helps threaded application work interact efficiently with Gtk+ interfaces.

The MVC pattern is also used in Gtk+ library itself. If you read the GtkTreeView documentation you'll see it mentioned and it's modularity enables the use of an unique model (the TreeStore or ListStore) with a GtkTreeView or a GtkComboBoxEntry.

Example 1: Background is The Way

Let's imagine we're dealing with an application that is working on a lengthy computation. Threaded programming helps your applications' graphical interface run smoothly while your computation is running in the background.

gboolean lengthy_func_done(gpointer data)
{
	GtkWidget *dialog;
	dialog = gtk_message_dialog_new(NULL,
                                  GTK_DIALOG_DESTROY_WITH_PARENT,
                                  GTK_MESSAGE_INFO,
                                  GTK_BUTTONS_CLOSE,
                                  "Operation completed!");
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	return FALSE;
}
gpointer async_lengthy_func(gpointer data)
{
	lengthy_func();
	g_idle_add(lengthy_func_done, NULL);
	return NULL;
}
void on_clicked(GtkWidget *buton, gpointer data)
{
	g_thread_create(async_lengthy_func, NULL, FALSE, NULL);
}

When the user clicks the button the function on_clicked is called. When this event happens we create a new thread, with g_thread_create. The thread's body is function async_lengthy_func.

NOTE: Every graphical related call must be performed inside the Gtk+ main loop. There are mechanisms for running callback functions inside it, i.e. using g_idle_add.

After lengthy_func is done, inside the new thread, we ask Gtk+ main loop to run another callback which will take care of warning the user we're finished. We used g_idle_add for routing our callback inside the main loop and the callback used was lengthy_func_done.

Finally, when Gtk+ main loop has an opportunity lengthy_func_done is called and then we show the message box informing the user that the operation was done.

Example 2: One At a Time

The way our first example was implemented, if the user clicks more then once the button, lengthy_func is called concurrently those many times. If we want to avoid this behaviour we must disable this opportunity from the user.

One way of blocking concurrent calls is to turn the button insensitive, this is a simple, yet effective way of solving the problem, but it is not advised on tasks that take a bit more then 3 seconds, because the user communication is minimal.

gboolean lengthy_func_done(gpointer data)
{
	GtkWidget *dialog;
	dialog = gtk_message_dialog_new(NULL,
                                  GTK_DIALOG_DESTROY_WITH_PARENT,
                                  GTK_MESSAGE_INFO,
                                  GTK_BUTTONS_CLOSE,
                                  "Operation completed!");
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	/* enable our button again */
	gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE);
	return FALSE;
}
gpointer async_lengthy_func(gpointer data)
{
	lengthy_func();
	g_idle_add(lengthy_func_done, data);
	return NULL;
}
void on_clicked(GtkWidget *buton, gpointer data)
{
	/* disable our button */
	gtk_widget_set_sensitive(button, FALSE);
	g_thread_create(async_lengthy_func, button, FALSE, NULL);
}

Before creating the working thread we turn the button insensitive. We send the button as user data through our many callbacks. When the callback that warns the user is called, we warn the user and then turn our button sensitive again.

Example 3: Let's Be User Friendly

When your computation lasts from 3~5 seconds and you cannot provide a progress indication you should at least present the user a dialog which warns him that it will take a while before the computation finishes. To do so we create a modal window which disables all access to parent window. Since the parent window is not accessible we don't need to turn the button insensitive, this corresponds to our first change in the code:

/* we no longer need to send the button as user data */
g_signal_connect(button, "clicked", G_CALLBACK(on_clicked), NULL);

Now after the button is clicked the modal window must be created:

void on_clicked(GtkWidget *button, gpointer data)
{
  	/* disable our button */
	GtkWidget *win, *label;
	GdkCursor *cursor;
	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_modal(GTK_WINDOW(win), TRUE);
	gtk_window_set_title(GTK_WINDOW(win), "Progress");
	gtk_container_set_border_width(GTK_CONTAINER(win), 12);
	g_signal_connect(win, "delete_event", G_CALLBACK(gtk_true), NULL);
	label = gtk_label_new("Please wait a little...");
	gtk_widget_show(label);
	gtk_container_add(GTK_CONTAINER(win), label);
	gtk_widget_show(win);
	cursor = gdk_cursor_new(GDK_WATCH);
	gdk_window_set_cursor(win->window, cursor);
	gdk_cursor_unref(cursor);
	g_thread_create(async_lengthy_func, win, FALSE, NULL);
}

The window only contains a label that should explain the user he should wait a bit. To increase the visual feedback we also change the mouse cursor, on that window, to the watch.

We need to send the window we created as user data because it will be destroyed afterwards:

GtkWidget *w;
w = GTK_WIDGET(data);
gtk_widget_hide(w);
gtk_widget_destroy(w);

Example 4: Let's Progress

Computations that take more then 5 seconds must have a dialog with a progress bar indicating the user that the application did not hang. Calling lengthy_func there is no way to know it's progress, therefore we must use a pulse only progress. A pulse progress bar simply shows when there is some activity, because we cannot know that either we'll do this in a repetitive timed event. Remembering our golden rule to not change Gtk+ related state if we're not in the main loop we must have this repetitive and timed loop being run inside Gtk+'s main loop. The function g_idle_timeout_add does just that, you can set a callback which will be called from time to time inside the main loop. We pick the window of our last example and we add the progress bar. In the timed event we pulse the progress bar. When we're finished we must stop the timed task and destroy the dialog.

gboolean update_progress(gpointer data)
{
	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(data));
	return TRUE;
}
void on_clicked(GtkWidget *button, gpointer data)
{
	GtkWidget *win, *w, *vbox;
	guint sid;
	/* create the modal window which warns the user to wait */
	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_modal(GTK_WINDOW(win), TRUE);
	gtk_window_set_title(GTK_WINDOW(win), "Progress");
	gtk_container_set_border_width(GTK_CONTAINER(win), 12);
	g_signal_connect(win, "delete_event", G_CALLBACK(gtk_true), NULL);
	vbox = gtk_vbox_new(FALSE, 12);
	gtk_widget_show(vbox);
	/* create label */
	w = gtk_label_new("Please wait...");
	gtk_widget_show(w);
	gtk_container_add(GTK_CONTAINER(vbox), w);
	/* create progress bar */
	w = gtk_progress_bar_new();
	gtk_widget_show(w);
	gtk_container_add(GTK_CONTAINER(vbox), w);
	/* add vbox to dialog */
	gtk_container_add(GTK_CONTAINER(win), vbox);
	gtk_widget_show (win);
	/* refresh the progress dialog */
	sid = g_timeout_add(100, update_progress, w);
	g_object_set_data(G_OBJECT(win), "source_id", GINT_TO_POINTER(sid));
	g_thread_create(async_lengthy_func, win, FALSE, NULL);
}

Before hiding and destroying the dialog we must add this line:

/* stop pulsing progress bar */
g_source_remove(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "source_id")));

The first difference from our last example is that we create a GtkVBox and add the GtkLabel from the last example and a also a GtkProgressBar.

After this we create the timed repetitive callback with g_timeout_add. Our callback update_progress accesses the user data (the progress bar) and sends a pulse to it.

Whenever we call g_idle_add or g_timeout_add we are creating something called a GSource and attaching it to a GMainContext. A GSource is, making it simple, our callback function and it's associated user data. A GMainContext, in our case, is Gtk+'s main loop. But when we call one of these functions the returned value is an ID which identifies the created GSource. We need this ID to stop the timed event which sends a pulse to our progress bar. This is what that last line of code is used for.

The Other Way To Do It

Another way to interface threaded programming and Gtk+ is to issue the graphical related calls directly from the outside threads. To do this you have to lock the main loop, by surrounding it with gdk_threads_enter and gdk_threads_leave . All you Gtk+ related calls must be surrounded with gdk_threads_enter and gdk_threads_leave.

This way of working is not the advised way to do it because it's error prone, tends to be uneffective and clashes with the program modularity. Gdk explains the why's and how's more throughly in it's Gdk threads section.

Conclusion

One really annoying thing in our examples is that the user does not have the possibility to cancel the progress. When developing your Model, making it cancelable and mesurable (in terms of progress) is user friendly. Users sometimes don't want wait and there are times they simply can't. Disabling this possibility is big drawback in any application.

Resources

The four examples can be downloaded in a tarball gtk_threads.tar.gz.


Tags used:

Back to top