GTK3和multithreading,取代已弃用的function

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

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

 gdk_threads_add_idle() 

要么

 gdk_threads_add_timeout() 

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

单击按钮 – >在先前的线程中启动工作器function – >在GUI窗口中更新大量的GUI元素。

你有3种方法可以做到:

  1. 在按钮回调中进行计算并使用gtk_event_pending() / gtk_main_iteration()

  2. 使用g_idle_add()或其他,以及gtk_event_pending() / gtk_main_iteration()

  3. 使用线程,最终使用互斥锁,以及g_idle_add()或其他。 通常,不需要互斥锁,但它可以解决一些错误或Heisenbugs 。

第三种解决方案似乎是最好的,因为使用前两种方法,在计算运行时退出应用程序时遇到了一些问题。 应用程序没有退出并打印了很多“ 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循环中使用工作线程时,以及在使用工作线程中的gdk_threads_add_idle()g_idle_add()添加到主循环的函数中更新GUI时,您可能必须使用互斥锁来锁定对GUI的访问,因为访问GUI的function之间可能存在冲突。 必须使用g_mutex_init(&mutex_interface);初始化互斥锁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以按特定顺序执行的function,则需要添加两个计数器并为使用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的情况,它似乎工作。

文档说的是你仍然可以在一个线程中运行你的worker函数,你不能使用该线程中的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... 

如果这对于您的用例来说太复杂了,并且您的工作线程中的计算经常将控制权返回给您的工作线程(比如,您正在循环中计算某些内容),那么您可以完全在主体中运行计算通过简单地将控制权返回到GUI主循环而没有锁定GUI的线程,如下所示:

 for (lots of items) { result = do_short_calculation_on(one_item); update_gui(result); while (gtk_events_pending()) gtk_main_iteration(); } 

当我关闭主窗口时出现此运行错误:Gtk-CRITICAL **:gtk_widget_get_parent:断言’GTK_IS_WIDGET(小部件)’失败

我想我找到了一个解决方案,使用两个全局变量来指示回调停止并调用gtk_main_quit() ,并将主窗口的“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”信号而不是“destroy”信号。