【问题标题】:GtkStatusBar gets stuck at a /random/ position and wants me to mousehover buttonGtkStatusBar 卡在 /random/ 位置并希望我将鼠标悬停在按钮上
【发布时间】:2015-03-28 18:01:57
【问题描述】:

我会保持简单

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

我正在使用来自回调函数on_starter_clickedg_thread_new("processing", GThreadFunc, NULL); 在线程中调用inside_thread


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

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

可能出了什么问题?

【问题讨论】:

  • 一些图形包要求所有 UI 更新都发生在主线程上。
  • 这是如何解释这种行为的?它更新到一个点,然后冻结并等待我将鼠标悬停或悬停在按钮上以使其继续。
  • 你正在与 UI 线程竞争;如果你输了比赛,任何事情都可能发生。碰巧不更新视图就是其中之一,可能是因为您在绘制一次时更新了该值,因此它认为不需要再次绘制。我不确定。您可以尝试使用 gdk_threads_idle_add() 更新进度条,这将安排回调在 UI 线程上运行。
  • 我之前尝试过两次gdk_threads_idle_add(),但没有任何反应。它没有用。可能是因为缺少例子……我搞错了。
  • 哦,对了,gdk_threads_add_idle() 是异步的,所以你的进度条会在未来的某个时候更新。如果你想使用进度条,你要么必须 a) 添加某种同步机制,要么 b) 将你的线程更改为 gdk_threads_add_idle() 回调(你可能会发现它的重复功能在这里很有用)。是的,GTK+ 不是 多线程安全的;一切都必须在调用gtk_init()的线程上完成。

标签: c windows gcc gtk gtk3


【解决方案1】:

不,您不能从另一个线程更新 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。完全放弃线程并循环空闲回调

如果空闲回调返回G_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 参数用于空闲回调和线程来传递这些函数需要的数据。请注意不要将指向局部变量的指针传递给空闲回调,因为这可能会在您的函数返回后运行(并且变量超出范围/不再存在)。

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

【讨论】:

  • 我不能更新进度条或者我不应该从另一个线程更新进度条?因为这就是我正在做的事情……而且行之有效。
  • 另外这两种方法(假设它们确实有效)我可以看到会比我目前的方法慢。 (2) 处理可能是巨大的,这就是为什么..人们添加进度条,这就是为什么它必须在另一个线程中 - 为了性能。 (1)此外,调度进程而不是调度进度条不是任何偏好..因为进程仍然是程序中最重要的部分,而不是一些图形指示。
【解决方案2】:

这个问题的答案非常具体。假设您想根据循环的进度更新进度条,您必须做 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+ 会安排更新,这是完全可以理解的,但这并不意味着“立即”。

【讨论】:

  • 嗯,你确定要这样回答你自己的问题吗,用我们的 cmets 构建的第三人称?而且这个答案并不能解决您所说的关于 GtkProgressBar 仅在线程末尾重绘的问题。
  • 这解决了我的问题?除了我,还有谁能证实。我也知道为什么它会修复它。评论有帮助。是的..当然,如果循环在主线程中,进度条会在循环之后更新。
  • 在第三人称与社区交谈/如果/有人遇到这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-04
  • 1970-01-01
  • 1970-01-01
  • 2012-07-07
  • 2011-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多