1

我正在尝试学习如何使用线程模块。我按照这里的说明进行操作:http: //effbot.org/zone/tkinter-threads.htm

我希望测试脚本将:

  1. 每两秒打印一次“计数”
  2. 显示一个弹出对话框窗口(也每 2 秒)
  3. 弹出窗口应该允许累积(如果我一段时间不点击“确定”,应该有多个弹出窗口)

但是,当我运行此脚本时,它会冻结主窗口并在一段时间后崩溃。我想我没有正确实现线程模块。

有人可以看看并指出我做错了什么吗?

这是我迄今为止尝试过的:

from Tkinter import *
import thread
import Queue
import time

class TestApp:
    def __init__(self, parent):
        self.super_Parent = parent
        self.main_container = Frame(parent)
        self.main_container.pack()
        self.top_frame = Frame(self.main_container)
        self.top_frame.pack(side=TOP)
        self.bottom_frame = Frame(self.main_container)
        self.bottom_frame.pack(side=TOP)
        self.text_box = Text(self.top_frame)
        self.text_box.config(height=20, width=20)
        self.text_box.pack()
        self.queue = Queue.Queue()
        self.update_me()

    def show_popup(self):
        self.my_popup = Toplevel(self.main_container)
        self.my_popup.geometry('100x100')
        self.popup_label = Label(self.my_popup, text="Hello!")
        self.popup_label.pack(side=TOP)
        self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
        self.pop_button.pack(side=TOP)

    def write(self, line):
        self.queue.put(line)

    def update_me(self):
        try:
            while 1:
                line = self.queue.get_nowait()
                if line is None:
                    self.text_box.delete(1.0, END)
                else:
                    self.text_box.insert(END, str(line))
                self.text_box.see(END)
                self.text_box.update_idletasks()
        except Queue.Empty:
            pass
        self.text_box.after(100, self.update_me)

def pipeToWidget(input, widget):
    widget.write(input)

def start_thread():
    thread.start_new(start_test, (widget,))

def start_test(widget):
    count = 0
    while True:
        pipeToWidget(str(count) + "\n", widget)
        count += 1
        time.sleep(2)
        widget.show_popup()

root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
4

1 回答 1

3

我无法重现您的问题,但我可以看到它为什么会发生。

您正在使用queue将消息从后台线程传递到主线程进行更新text_box,这是正确的。但是您也是widget.show_popup()从后台线程调用的,这意味着它会Toplevel在后台线程中创建并显示一个新线程。这是正确的。

所有 UI 代码必须在同一个线程中运行——不是每个顶级窗口的所有 UI 代码,所有 UI 代码周期。在某些平台上,您可能会在自己的线程中运行每个窗口(甚至是自由线程),但这不应该工作,并且肯定会在某些平台上崩溃或做不正确的事情。(此外,该单个 UI 线程必须是某些平台上的初始线程,但这与此处无关。)


因此,要解决此问题,您需要像创建更新文本框一样创建弹出窗口。

显而易见的方法是widget.show_popup()update_me(). 如果您希望它在文本框更新后 2 秒发生,只需添加self.top_frame.after(2000, self.show_popup)到方法中即可。

但我猜您正在尝试自学如何拥有多个独立的更新机制,因此告诉您“只对所有内容使用单个更新队列”可能不是一个好的答案。在这种情况下,只需创建两个队列,并为每个队列提供一个单独的更新方法。然后,做你的pipeToWidget,睡 2 秒,然后pipeToPopup


解决此问题的另一种方法是使用mtTkinter. 它基本上完全按照您正在做的事情进行,但使其自动化,将每个实际的 Tk GUI 调用推送到一个队列中,以便稍后由主循环运行。当然,您的对象本身必须是线程安全的,这也意味着您必须处理来自一个线程的 GUI 调用与来自另一个线程的调用交错。但只要这些都不是问题(而且它们似乎不是你的情况),它就像魔术一样。


如果你想知道为什么这在 Win7 上对你来说是冻结和/或崩溃,而不是在 OS X 10.8 上对我来说......好吧,你真的需要研究一堆 Tcl、C 和 Python 代码,以及每个东西建好了。而且,除非它很简单(比如你的 Tk 构建不是自由线程的),否则它不会告诉你太多。代码不应该工作,如果它似乎对我有用……那可能只是意味着它每次都能工作,直到我职业生涯中最重要的演示,到那时它会失败。

于 2013-04-05T22:37:07.317 回答