GtkStatusBar 卡在 /random/ 位置并希望我将鼠标悬停按钮

GtkStatusBar gets stuck at a /random/ position and wants me to mousehover button

我会保持简单

我有函数 inside_thread 运行 while 循环并调用函数 update_progressbar 来简单地更新 GtkStatusBar.

我正在使用 g_thread_new("processing", GThreadFunc, NULL); 从回调函数 on_starter_clicked.

在线程中调用 inside_thread

它倾向于完成工作,工作速度很快,它会根据 while 循环的进度更新状态栏。

问题是有时(在未指定的地方)进度条卡住,如果我在应用程序中使用鼠标输入或鼠标离开按钮,它又会松开。我必须承认非常奇怪的行为

有什么问题吗?

这个问题的答案很具体。假设您想根据循环的进度更新进度条,您必须做 3 件事。

  • 创建一个线程,您将在其中 运行 您的循环
    • 通过调试确保循环,如果需要互斥锁
  • [a] 当 GTK 能够使用 gdk_threads_add_idle () 时安排进度条更新 请注意,此函数将使用 G_PRIORITY_DEFAULT_IDLE 调用进度条更新函数并顺其自然。否则如果你好奇地看看 g_idle_add_full ()
  • [b]不再需要时终止线程(进度100%)

GTK+ 不是多线程安全的。它不能总是重新绘制组件,特别是当它很忙时,在主循环的未指定迭代中陷入僵局。这是一个同步问题,这就是为什么你需要安排更新。


[a] 由于您要为进度条更新函数提供值以进行区间运算,因此您必须将它们作为数组传递给功能。

[b] 另一个问题发生在整个过程的最后。除非您确保不再更新进度条,否则您的应用程序将崩溃。您可以暗示互斥锁或您提出的任何类型的限制。 重要提示:这不是处理后崩溃的唯一原因。正如另一个答案明确指出的那样……您不能只通过另一个线程更新 gtk 组件。要忽略这一点,只需在循环之后、线程终止之前调用 gtk_main_iteration(),这样它就会再迭代一次以正确及时地更新进度条。


整个事情的伪代码实现的简单示例:

gboolean on_starter_clicked () // callback function
{
    g_thread_new("processing", proceed_thread, NULL);
}

gpointer proceed_thread (gpointer data) // processing-thread
{
    while(1)
    {
        // Process whatever needs to be processed
        // break when it needs to be breaked

        gint data[] = { partial, full };
        gdk_threads_add_idle(update_progressbar, data);
    }
    gtk_main_teration();
}

gboolean update_progressbar (gpointer data) // updating function
{
    gint *d = (gint*)data;
    gdouble fraction = ( (d[0] * 100.0) / d[1] ); // Calculating your interval

    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(...), fraction / 100.0 );
}

还有一件事。您可能会注意到进度指示器的移动平滑度会略有下降。那是因为 GTK+ 安排更新,这是完全可以理解的,并不意味着 "immediately".

不,您不能从另一个线程更新 GtkProgressBar。您的问题有两种解决方案,都涉及 gdk_threads_idle_add():

1.等待空闲回调

在这种情况下,您的线程将使用 gdk_threads_add_idle() 安排进度条更新,然后等待它完成。我不知道使用 GLib 执行此操作的最佳方法是什么,但您可能可以做一些事情(GMutex?)。思路是这样的:

gboolean updateProgressBar(gpointer data)
{
    gtk_progress_bar_set_fraction(progressbar, value);
    tellOtherThreadToContinue();
    return G_SOURCE_REMOVE;        // == FALSE
}

gpointer otherThread(gpointer data)
{
    while (condition) {
        doStep();
        gdk_threads_idle_add(updateProgressBar, NULL);
        waitForProgressBarToBeUpdated();
    }
    return (gpointer) 0;
}

2。完全放弃线程并循环空闲回调

如果空闲回调returnsG_SOURCE_CONTINUE(==TRUE),GTK+会在下一个空闲周期再次调度你的函数。您可以利用这一点将 while 循环重写为空闲回调:

gboolean loop(gpointer data)
{
    if (condition)
        return G_SOURCE_REMOVE;
    doStep();
    gtk_progress_bar_set_fraction(progressbar, value);
    return G_SOURCE_CONTINUE;
}

// in your code, start the loop going with
gdk_threads_idle_add(loop, NULL);

无论您选择什么,您都可以利用空闲回调和线程的 data 参数来传递这些函数需要的数据。请注意不要将指向局部变量的指针传递给空闲回调,因为这可能 运行 在您的函数 returns 之后(并且该变量超出 scope/ceases 存在)。

请注意,无论您做什么,您都将等待进度条更新(在后一种情况下,会重新绘制)。如果事情变得太慢,您可能需要考虑分块更新进度条。但是,这完全取决于您的步数。