我正在尝试学习如何使用线程模块。我按照这里的说明进行操作:http://effbot.org/zone/tkinter-threads.htm
我希望测试脚本会:
但是,当我运行此脚本时,它会冻结主窗口并在一段时间后崩溃。我想我没有正确实现线程模块。
有人可以看看并指出我做错了吗?
这是我到目前为止所尝试的内容:
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()
答案 0 :(得分: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构建不是自由线程的),否则它不会告诉你太多。代码不应该起作用,如果它似乎对我有用......那可能只是意味着它每次都会起作用,直到我职业生涯中最重要的演示,此时它会失败。