Executing GTK functions from other threads

人盡茶涼 提交于 2020-05-28 06:39:07

问题


This question is about GTK and threads. You may find it useful if your application crashes, freezes or you want to have a multithreaded GTK application.


回答1:


Main Loop

In order to understand GTK you must understand 2 concepts.

  1. All contemporary GUIs are single-threaded. They have a thread which processes events from window system (like button, mouse events). Such a thread is called main event loop or main loop. GTK is also single threaded and not MT-safe. This means, that you must not call any GTK functions from other threads, as it will lead to undefined behaviour.

  2. As Gtk documentation sates,

    Like all GUI toolkits, GTK+ uses an event-driven programming model. When the user is doing nothing, GTK+ sits in the “main loop” and waits for input. If the user performs some action - say, a mouse click - then the main loop “wakes up” and delivers an event to GTK+. GTK+ forwards the event to one or more widgets.

Gtk is event-based and asynchronous. It reacts to button clicks not in the exact moment of clicking, but a bit later.

It can be very roughly written like this (don't try this at home):

static list *pollable;

int main_loop (void)
{
    while (run)
        {
            lock_mutex()
            event_list = poll (pollable); // check whether there are some events to react to
            unlock_mutex()
            dispatch (event_list);        // react to events.
        }
}

void schedule (gpointer function)
{
    lock_mutex()
    add_to_list (pollable, something);
    unlock_mutex()
}

I want a delayed action in my app

For example, hide a tooltip in several seconds or change button text. Assuming your application is single-threaded, if you call sleep() it will be executed in main loop. sleep() means, that this particular thread will be suspended for specified amount of seconds. No work will be done. And if this thread is main thread, GTK will not be able to redraw or react to user interactions. The application freezes.

What you should do is schedule function call. It can be done with g_timeout_add or g_idle_add In the first case our poll() from snippet above will return this event in several seconds. In the latter case it will be returned when there are no events of higher priority.

static int count;

gboolean change_label (gpointer data)
{
    GtkButton *button = data;
    gchar *text = g_strdup_printf ("%i seconds left", --count);
    if (count == 0)
        return G_SOURCE_REMOVE;
    return G_SOURCE_CONTINUE; 
}

void button_clicked (GtkButton *button)
{
    gtk_button_set_label (button, "clicked");
    count = 5;
    g_timeout_add (1 * G_TIME_SPAN_SECOND, change_label, button);
}

Returning a value from function is very important. If you don't do it, the function will be called again and again.

I have additional threads and my app crashes

As already mentioned, GTK is not MT-safe. You must not call Gtk functions from other threads. You must schedule execution. g_timeout_add and g_idle_add are MT-safe, unlike other GTK functions. That callbacks will be executed in main loop. If you have some shared resources between callback and thread you must read/write them atomically or use a mutex.

static int data;
static GMutex mutex;

gboolean change_label (gpointer data)
{
    GtkButton *button = data;
    int value;
    gchar *text;

    // retrieve data
    g_mutex_lock (&mutex);
    value = data;
    g_mutex_unlock (&mutex);

    // update widget
    text = g_strdup_printf ("%i seconds left", --count);
    return G_SOURCE_REMOVE;
}

gpointer thread_func (gpointer data)
{
    GtkButton *button = data;
    while (TRUE)
        {
            sleep (rand_time);
            g_mutex_lock (&mutex);
            ++data;
            g_mutex_unlock (&mutex);
            g_idle_add (change_label, button);
        }
}

Follow up: but python is single-threaded and GIL et cetera?

You can imagine that python is multi-threaded application run on a single-core machine. You never know when the threads will be switched. You call a GTK function but you don't know in which state the main loop is. Maybe it free'd resources just a moment before. Always schedule.

What is not discussed and further reading

  • Detailed documentation on glib main loop can be found here
  • GSource as a more low-level primitive.
  • GTask


来源:https://stackoverflow.com/questions/60949269/executing-gtk-functions-from-other-threads

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!