Выполнение функций GTK из других потоков - PullRequest
1 голос
/ 31 марта 2020

Этот вопрос о GTK и темах. Это может оказаться полезным, если ваше приложение вылетает, зависает или вы хотите иметь многопоточное приложение GTK.

1 Ответ

1 голос
/ 31 марта 2020

Main L oop

Для понимания GTK необходимо понимать 2 понятия.

  1. Все современные графические интерфейсы являются однопоточными. У них есть поток, который обрабатывает события из оконной системы (например, кнопки, события мыши). Такой поток называется main event l oop или main l oop. GTK также однопоточный и не MT-безопасный. Это означает, что вы не должны вызывать какие-либо функции GTK из других потоков, так как это приведет к неопределенному поведению.

  2. Как указано в документации Gtk,

    Like Во всех GUI наборах инструментов GTK + использует модель программирования, управляемую событиями. Когда пользователь ничего не делает, GTK + сидит в «основном цикле» и ждет ввода. Если пользователь выполняет какое-либо действие, скажем, щелчком мыши, то основное l oop «просыпается» и доставляет событие в GTK +. GTK + пересылает событие одному или нескольким виджетам.

Gtk основан на событиях и асинхронен. Он реагирует на нажатия кнопок не в точный момент нажатия, а чуть позже.

Это может быть очень грубо написано так (не пытайтесь сделать это дома):

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

Я хочу отложенное действие в моем приложении

Например, скрыть всплывающую подсказку за несколько секунд или изменить текст кнопки. Предполагая, что ваше приложение однопоточное, если вы вызовете sleep(), оно будет выполнено в основном l oop. sleep() означает, что этот конкретный поток будет приостановлен на указанное количество секунд. Никакой работы не будет сделано. И если этот поток является основным потоком, GTK не сможет перерисовывать или реагировать на взаимодействие с пользователем. Приложение зависает.

Что вам нужно сделать, это расписание вызов функции. Это можно сделать с помощью g_timeout_add или g_idle_add В первом случае наш poll() из приведенного выше фрагмента вернет это событие через несколько секунд. В последнем случае он будет возвращен, когда нет событий с более высоким приоритетом.

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

Возвращение значения из функции очень важно . Если вы этого не сделаете, функция будет вызываться снова и снова.

У меня есть дополнительные темы, и мое приложение вылетает

Как уже упоминалось, GTK не является MT-безопасным. Вы не должны вызывать функции Gtk из других потоков. Вы должны запланировать выполнение. g_timeout_add и g_idle_add являются MT-безопасными, в отличие от других функций GTK. Это обратные вызовы будут выполняться в основном l oop. Если у вас есть общие ресурсы между обратным вызовом и потоком, вы должны читать / записывать их атомарно или использовать мьютекс.

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: но python является однопоточным и GIL и так далее?

Вы можете себе представить, что python - это многопоточное приложение, работающее на одноядерном компьютере. Вы никогда не знаете, когда потоки будут переключены. Вы вызываете функцию GTK, но не знаете, в каком состоянии находится главный l oop. Может быть, это бесплатные ресурсы за мгновение до этого. Всегда график.

Что не обсуждается и что дальше читается

  • Подробную документацию по glib main l oop можно найти здесь
  • GSource как более низкоуровневый примитив.
  • GTask
...