【问题标题】:Destroy Tkinter window in thread在线程中销毁 Tkinter 窗口
【发布时间】:2021-03-08 12:09:48
【问题描述】:

我有一个项目,其中被动 GUI 在其自己的线程中运行并由主线程操作。特别是主线程使用event_generate关闭窗口:

from tkinter import Tk
import threading
import time
import queue

q = queue.Queue()


class Window:
    def __init__(self):
        self.root = Tk()
        self.root.title("test")
        self.root.bind("<<custom_close_event>>", self.close)

    def close(self, event):
        print("quit")
        self.root.destroy()


def create_window():
    window = Window()
    q.put(window)
    window.root.mainloop()
    print("###########")


# Window creation executed in different thread
t1 = threading.Thread(target=create_window)
t1.start()

window = q.get()

time.sleep(2)

window.root.event_generate("<<custom_close_event>>")

print("end")

程序崩溃,输出如下:

quit
###########
Tcl_AsyncDelete: async handler deleted by the wrong thread
[1]    21572 IOT instruction (core dumped)  python window_test.py

根据this discussion,看来是多线程环境下对象清理的顺序有问题。使对象无效(在我的情况下为 window)和调用 gc.collect 的建议并没有解决问题。

我该怎么办?

【问题讨论】:

  • 你不应该从不同的线程访问 tkinter 对象。
  • event_generate 可以根据documentation(线程或进程部分)从另一个线程安全地调用。
  • 是的,但是对某些对象的引用仍然存在,这就是 tcl 崩溃的原因

标签: python multithreading tkinter


【解决方案1】:

而不是使用单独的线程来创建对 Tk() 的第二个引用, 只需在创建“Window”类时继承tk.Toplevel。 这将允许您拥有任意数量的窗口。 您可以使用tk.after 来监控进程并执行伪多线程操作。这是一个如何做到这一点的示例

class Window(Toplevel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parent = parent
        ...
        self.parent.after(1000, self.do_something)
 
    def do_something(self):
        ...
        <code>
        ...
        self.parent.after(1000, self.do_something)
    
root = Tk()
Window(root)
root.mainloop()

【讨论】:

  • 创建实例时我应该传递什么parent
  • 它可以是任何你想要的!请记住,只要父级被销毁(关闭),此窗口就会被销毁
  • 如果我通过 Tk 实例,我会得到 2 个窗口,这不是我想要的......
  • 哦,对了。我稍微误解了你问的内容。以为您想要一个与主窗口分开的窗口。但我猜你只想要 1 个窗口。
【解决方案2】:

使用@AndrewPye 的答案,但继承自Tk 而不是Toplevel

from tkinter import *

class Window(Tk):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        super().after(1000, self.do_something)

    def do_something(self):
        print("I am in a loop that runs every 1000ms = 1s")

        super().after(1000, self.do_something)

root = Window()
root.mainloop()

【讨论】:

  • 我认为你可以使用self.after,不是吗?
  • @Neraste 是的,但我更愿意明确说明我正在使用我继承的类的 .after 方法。
猜你喜欢
  • 2020-02-22
  • 1970-01-01
  • 1970-01-01
  • 2018-02-01
  • 2016-12-07
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2021-09-24
相关资源
最近更新 更多