GTK3和多线程,取代已弃用的功能

Sam*_*ri 8 c multithreading gdk gtk3

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

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

gdk_threads_add_idle() 
Run Code Online (Sandbox Code Playgroud)

要么

gdk_threads_add_timeout() 
Run Code Online (Sandbox Code Playgroud)

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

单击按钮 - >在先前的线程中启动工作器功能 - >在GUI窗口中更新大量的GUI元素.

Ber*_*125 10

你有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循环工作线程,可以直接使按钮回调的计算,更新的图形用户界面和与它处理事件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();
  }
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

3.线程和互斥:

一些使用线程的情况下作出的计算要快,所以在主GTK循环使用时,一个工作线程NOT,并在功能更新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);
}
Run Code Online (Sandbox Code Playgroud)

如果您需要更新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);
}
Run Code Online (Sandbox Code Playgroud)

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


pto*_*ato 9

文档说的是你仍然可以在一个线程中运行你的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...
Run Code Online (Sandbox Code Playgroud)

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

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

    update_gui(result);

    while (gtk_events_pending())
        gtk_main_iteration();
}
Run Code Online (Sandbox Code Playgroud)