gtk idle_add 不运行?

5 投票
3 回答
5041 浏览
提问于 2025-04-16 03:21

我有一个使用两个线程的应用程序:一个是图形界面(GUI),另一个是一些后台工作。我想给主线程发送请求,以便更新图形界面(比如移动进度条),但似乎没有效果。我把问题简化到了一个非常简单的例子:

import pygtk
pygtk.require('2.0')
import glib
import gtk

import threading
import sys
import time

def idle():
    sys.stderr.write('Hello from another world.\n')
    sys.stderr.flush()
    gtk.main_quit()

def another_thread():
    time.sleep(1)
    glib.idle_add(idle)

thread = threading.Thread(target=another_thread)
thread.start()
gtk.main()

我原以为这段代码会从主线程/图形界面线程打印一些东西到标准错误输出,但什么都没发生。而且程序也没有退出,所以gtk.main_quit这个函数没有被调用。

另外,往stderr添加更多输出时,表现得也很奇怪。如果我把线程的函数改成:

sys.stderr.write('----\n')
sys.stderr.write('----\n')
sys.stderr.flush()
sys.stderr.write('After.\n')
sys.stderr.flush()

我看到输出中有1行,有时是2行。这看起来像是主线程进入gtk.main时出现了一种竞争条件,但我不知道为什么会这样。

3 个回答

3

为什么不直接用 glib.timeout_add_seconds(1, idle) 并在 idle() 里返回 False,而是要启动一个线程然后让它睡1秒呢?从另一个线程启动一个空闲函数其实是多余的,因为空闲函数本身就已经在另一个线程里运行了。

编辑:

我说“从另一个线程启动空闲函数是多余的”,是指你不需要启动一个空闲函数就能操作图形界面(GUI)和更新进度条。认为在其他线程中不能操作图形界面是个误解。

让我再说一下如何在其他线程中调用GTK的函数:

  1. 调用 glib.threads_init()gobject.threads_init(),具体用哪个取决于你使用的库,正如vanza的回答中提到的。
  2. 调用 gtk.gdk.threads_init()。我很确定你在回答中是对的,这个函数只需要在 gtk.main() 之前调用。C文档建议在 gtk_init() 之前调用,但如果我没记错的话,PyGTK在导入时就会调用这个函数。
  3. 把对 gtk.main() 的调用放在 gtk.gdk.threads_enter()gtk.gdk.threads_leave() 之间。
  4. 把从以下地方调用GTK函数的代码放在:

    • 主线程以外的线程
    • 空闲函数
    • 超时函数
    • 基本上除了信号处理器以外的任何回调

    也放在 gtk.gdk.threads_enter()gtk.gdk.threads_leave() 之间。

注意,除了用 enter/leave 来包裹你的调用外,你也可以使用 with gtk.gdk.lock:,然后在这个 with 块中进行调用。

这里有一些资源也解释了这个问题:

4

在多线程环境中使用glib之前,你需要先初始化glib的线程支持。只需要调用一下:

glib.threads_init()

然后再使用glib的函数。

0

以下内容对我来说是有效的:

在做任何事情之前,先调用一下 gtk.gdk.threads_init():在启动线程之前,或者在进入 gtk.main() 之前。我觉得实际上,这个函数只需要在进入 gtk.main() 之前调用就可以了,但在一切都很简单、只有一个线程的时候调用它也很方便。

在空闲回调函数中(示例中的 idle),在处理GTK相关的内容之前,先调用 gtk.gdk.threads_enter()(可以很简单地放在函数的开头),然后在函数结束之前调用 gtk.gdk.threads_leave()

调用 gtk.gdk.threads_init() 似乎还告诉 PyGTK 在休眠时不要占用全局解释器锁(GIL)——我觉得在示例中,我错过了一些来自辅助线程的输出,原因就是主线程在 gtk.main() 中休眠时仍然占用了 GIL。根据我的理解,gtk.gdk.threads_init() 为 PyGTK 和 GTK+ 带来了良好的效果。因此,即使你启动的线程不涉及 GTK+,也不处理 glib、gobject 等,只是进行基本的计算,调用 gtk.gdk.threads_init() 也是必要的。

撰写回答