对话框在线程中冻结整个应用程序,尽管使用了Gdk.threads_enter/leave()

0 投票
1 回答
522 浏览
提问于 2025-04-27 12:19

我有一个应用程序,它在后台做一些事情。为了让用户知道进展,它会更新一些小部件。这部分是正常工作的。

但是,有时候在这个后台操作中会出现错误或者其他问题,这时就需要显示一个对话框来提示用户。这会导致我的整个应用程序都卡住,尽管我已经用线程锁来处理所有事情。下面是一个代码示例,正好展示了我遇到的问题:

import threading, time
from gi.repository import Gtk, Gdk

def background(label, parent):
    for t in range(5):
        label.set_text(str(t))
        time.sleep(1)
    Gdk.threads_enter()
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")
    dlg.run()
    dlg.destroy()
    Gdk.threads_leave()

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    Gdk.threads_enter()
    thread.start()
    Gtk.main()
    Gdk.threads_leave()

if __name__=="__main__":
    main()
暂无标签

1 个回答

3

在gtk3中,所有像添加、删除或更改控件的操作都必须在gtk线程中执行,也就是运行Gtk.main()的那个线程。

修正后的代码:

import threading, time
from gi.repository import Gtk, Gdk, GLib # need GLib for GLib.PRIORITY_DEFAULT

# a short utility function that I like to use.
# makes the Gtk thread execute the given callback.
def add_mainloop_task(callback, *args):
    def cb(args):
        args[0](*args[1:])
        return False
    args= [callback]+list(args)
    Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT, cb, args)

def background(label, parent):
    for t in range(5):
        #~ label.set_text(str(t)) # let the gtk thread do this.
        add_mainloop_task(label.set_text, str(t))
        time.sleep(1)
    #~ Gdk.threads_enter() # don't need this.
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")

    # put these two functions calls inside a little function, and let the gtk thread execute it.
    def run_dialog(dlg):
        dlg.run()
        dlg.destroy()
    add_mainloop_task(run_dialog, dlg)
    #~ Gdk.threads_leave() # don't need this.

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    #~ Gdk.threads_enter() # don't need this.
    thread.start()
    Gtk.main()
    #~ Gdk.threads_leave() # don't need this.

if __name__=="__main__":
    main()

撰写回答