【问题标题】:Tkinter locks Python when an icon is loaded and tk.mainloop is in a threadTkinter 在加载图标并且 tk.mainloop 在线程中时锁定 Python
【发布时间】:2025-12-09 07:25:02
【问题描述】:

这是测试用例...

import Tkinter as tk
import thread
from time import sleep

if __name__ == '__main__':
    t = tk.Tk()
    thread.start_new_thread(t.mainloop, ())
    # t.iconbitmap('icon.ico')

    b = tk.Button(text='test', command=exit)
    b.grid(row=0)

    while 1:
        sleep(1)

此代码有效。取消注释 t.iconbitmap 行并锁定。以您喜欢的方式重新安排它;它会锁定。

当存在图标时,如何防止 tk.mainloop 锁定 GIL

目标是win32和Python 2.6.2。

【问题讨论】:

    标签: python winapi tkinter green-threads


    【解决方案1】:

    我相信你不应该在不同的线程上执行主循环。 AFAIK,主循环应该在创建小部件的同一线程上执行。

    我熟悉的 GUI 工具包(Tkinter、.NET Windows Forms)是这样的:您只能从一个线程操作 GUI。

    在 Linux 上,您的代码会引发异常:

    self.tk.mainloop(n) RuntimeError:从不同的公寓调用 Tcl

    以下之一将起作用(没有额外的线程):

    if __name__ == '__main__':
        t = tk.Tk()
        t.iconbitmap('icon.ico')
    
        b = tk.Button(text='test', command=exit)
        b.grid(row=0)
    
        t.mainloop()
    

    有额外的线程:

    def threadmain():
        t = tk.Tk()
        t.iconbitmap('icon.ico')
        b = tk.Button(text='test', command=exit)
        b.grid(row=0)
        t.mainloop()
    
    
    if __name__ == '__main__':
        thread.start_new_thread(threadmain, ())
    
        while 1:
            sleep(1)
    

    如果您需要从 tkinter 线程外部与 tkinter 进行通信,我建议您设置一个计时器来检查工作队列。

    这是一个例子:

    import Tkinter as tk
    import thread
    from time import sleep
    import Queue
    
    request_queue = Queue.Queue()
    result_queue = Queue.Queue()
    
    def submit_to_tkinter(callable, *args, **kwargs):
        request_queue.put((callable, args, kwargs))
        return result_queue.get()
    
    t = None
    def threadmain():
        global t
    
        def timertick():
            try:
                callable, args, kwargs = request_queue.get_nowait()
            except Queue.Empty:
                pass
            else:
                print "something in queue"
                retval = callable(*args, **kwargs)
                result_queue.put(retval)
    
            t.after(500, timertick)
    
        t = tk.Tk()
        t.configure(width=640, height=480)
        b = tk.Button(text='test', name='button', command=exit)
        b.place(x=0, y=0)
        timertick()
        t.mainloop()
    
    def foo():
        t.title("Hello world")
    
    def bar(button_text):
        t.children["button"].configure(text=button_text)
    
    def get_button_text():
        return t.children["button"]["text"]
    
    if __name__ == '__main__':
        thread.start_new_thread(threadmain, ())
    
        trigger = 0
        while 1:
            trigger += 1
    
            if trigger == 3:
                submit_to_tkinter(foo)
    
            if trigger == 5:
                submit_to_tkinter(bar, "changed")
    
            if trigger == 7:
                print submit_to_tkinter(get_button_text)
    
            sleep(1)
    

    【讨论】:

    • 嗯,你已经一针见血了,它有效......但我因没有提供足够的信息而受苦。我的理由是我希望能够在 while 循环所在的地方对 tkinter 做一些事情……对 SO 有点陌生,我应该接受你的回答并问另一个更详细的问题吗?
    • 嗨,我已经用一个建议和代码示例更新了我的答案,以实现这一点。 while 循环现在调用 tkinter 线程上的一些方法,使用请求/响应队列。
    • 顺便说一句,对于生产代码,我建议您将 Tkinter 窗口、线程和队列封装在一个类中。这是为了避免我们现在拥有的全局变量:request_queue、response_queue 和 t。您还需要围绕 callable(*args, **kwargs) 进行一些错误处理。
    • 我会很高兴简单地说“你应该为此使用队列”,你已经远远超出了职责范围,谢谢。
    最近更新 更多