GTK3 和多线程,替换已弃用的函数

GTK3 and multithreading, replacing deprecated functions

我想在使用线程的应用程序中替换已弃用的函数 gdk_threads_enter()/leave()。现在的应用程序运行完美(虽然我不确定这是否是正确的方法)。

我的主循环,运行是 gtk_main 和信号处理程序。当我收到一个开始按钮时,我会启动一个线程,运行s 在主线程的后台。我如何从该线程更新 GUI。我知道根据 GTK3 和 GDK3 的文档,他们说通过使用

来避免它
gdk_threads_add_idle() 

gdk_threads_add_timeout() 

但是,如果我希望只有在单击开始时才进行更新,我该怎么做呢? 有没有例子。我不是在问如何使用 gdk_threads_add_idle(),我是在问如何 运行 在单击开始后没有线程的情况下主线程中的工作函数。

单击按钮 --> 启动 worker 函数 "in thread previously" --> 更新 GUI 中的大量 GUI 元素 window.

文档中所说的是您仍然可以 运行 在线程中使用辅助函数,只是不能从该线程使用 GTK 和 GDK 函数。因此,您仍然可以在单击开始时启动线程。但是不是从线程更新 GUI 元素,而是必须使用 gdk_threads_add_idle().

安排它们从主线程更新

所以你的图表应该是这样的:

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...

如果这对于您的用例来说太复杂,并且您的工作线程中有一个计算 returns 经常控制您的工作线程(例如,您正在循环计算某些东西),那么您可以 运行 完全在主线程中进行计算,而无需通过将控制短暂返回到 GUI 主循环来锁定 GUI,如下所示:

for (lots of items) {
    result = do_short_calculation_on(one_item);

    update_gui(result);

    while (gtk_events_pending())
        gtk_main_iteration();
}

您有 3 种方法可以做到:

  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.

第三种解决方案似乎是最好的,因为使用前两种方法,我在退出应用程序时遇到了一些问题,同时计算是 运行ning。该应用程序没有退出,并打印出大量“Gtk Critical”警告。 (我在 Windows 和 mingw32 上试过了。


1。按钮回调:

如果你想 运行 主 gtk 循环中的工作线程,你可以直接在按钮回调中进行计算,更新 GUI 并使用 gtk_event_pending()gtk_main_iteration(),如下示例代码:

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

您也可以使用 g_thread_new() 代替 gdk_thread_add_idle()(如果某些不受您控制的图书馆可能会使用 gdk_threads_enter()/leave())或 g_idle_add()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。线程和互斥:

一些情况下使用线程使计算速度更快,因此当使用不在主 gtk 循环中的工作线程时,以及在添加到函数中更新 GUI 时在工作线程中使用 gdk_threads_add_idle()g_idle_add() 的主循环,您可能必须使用互斥锁锁定对 GUI 的访问,因为访问 GUI 的函数之间可能存在冲突。在应用程序使用之前,必须使用 g_mutex_init(&mutex_interface); 初始化互斥锁。例如:

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

如果您需要更新 GUI 的函数以特定顺序执行,您需要添加两个计数器并为使用 g_idle_add()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);
}

我还测试了锁定单个小部件而不是整个 GUI 的情况,它似乎有效。

I get this running error when i close the main window: Gtk-CRITICAL **: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

我想我找到了一个解决方案,使用两个全局变量指示回调停止和调用 gtk_main_quit(),并捕获主要 window 的 "destroy" 信号] 在以下示例中转换为名为 gtk_main_quit2() 的自定义回调:

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

}

如果在单击关闭按钮后仍然有一些 gtk 警告,您可以尝试捕获 "delete-event" 信号而不是主 window 上的 "destroy" 信号。