这可能是我在这里问过的最复杂的问题了。我花了一些时间让我的代码变得最简单,我认为我可以重现我的问题。我希望得到任何帮助不会太复杂。
基本上在下面的代码中,创建了一个带有单个按钮的tkinter应用程序,并且它每100毫秒检查一个队列,因为不同的线程可能需要稍后与它交互。新窗口的创建和销毁也非常快,因为我稍后会得到一个错误,否则(这可能很重要)
当按钮被点击时,一个新线程被创建,它告诉主线程(通过队列)创建一个窗口,该窗口将用于指示可能耗时的事情正在发生,然后当它完成时,它告诉主线程(通过队列)销毁该窗口。
问题是,如果耗时的任务很短,也没有错误,窗口不会被破坏,但如果线程中的进程花费了很长时间(例如一秒钟),它会按预期工作。
我想知道它是否类似于"新的窗口对象尚未创建并分配给new_window
,所以当我将destroy方法添加到队列时,我实际上是将旧的(以前销毁的)对象的destroy方法添加到队列"。这就解释了为什么我得到一个错误,我第一次点击按钮,如果我不创建和销毁窗口,当我初始化应用程序,但它不能解释为什么我没有得到一个错误调用destroy()
上先前销毁的Toplevel
…如果我的理论是对的,我真的不知道解决方案是什么,所以任何想法都将是非常感谢的
import tkinter as tk
import queue
import threading
import time
def button_pressed():
threading.Thread(target=do_something_on_a_thread).start()
def do_something_on_a_thread():
global new_window
app_queue.put(create_a_new_window)
time.sleep(1)
app_queue.put(new_window.destroy)
def create_a_new_window():
global new_window
new_window = tk.Toplevel()
tk.Label(new_window, text='Temporary Window').grid()
#Check queue and run any function that happens to be in the queue
def check_queue():
while not app_queue.empty():
queue_item = app_queue.get()
queue_item()
app.after(100, check_queue)
#Create tkinter app with queue that is checked regularly
app_queue = queue.Queue()
app = tk.Tk()
tk.Button(app, text='Press Me', command=button_pressed).grid()
create_a_new_window()
new_window.destroy()
app.after(100, check_queue)
tk.mainloop()
你的理论听起来不错。你不会得到一个错误或警告调用.destroy
在一个先前被破坏的顶层窗口,因为Tkinter"有用的"不抱怨。:)
这是你的代码的一个版本,似乎工作,至少它没有留下不想要的窗口。我去掉了global
,把窗口推到一个堆栈上,这样我就可以在想要销毁它们的时候弹出它们。在你的实际代码中,你可能想循环遍历堆栈并检查窗口id,这样你就可以销毁正确的窗口id。
import tkinter as tk
import queue
import threading
import time
window_stack = []
def destroy_top_window():
print
if window_stack:
w = window_stack.pop()
print('destroy', w, len(window_stack))
w.destroy()
#time.sleep(1); w.destroy()
else:
print('Stack empty!')
def button_pressed():
threading.Thread(target=do_something_on_a_thread).start()
def do_something_on_a_thread():
app_queue.put(create_a_new_window)
time.sleep(1)
app_queue.put(destroy_top_window)
def create_a_new_window():
new_window = tk.Toplevel()
tk.Label(new_window, text='Temporary Window').grid()
window_stack.append(new_window)
print('create ', new_window, len(window_stack))
#Check queue and run any function that happens to be in the queue
def check_queue():
while not app_queue.empty():
queue_item = app_queue.get()
queue_item()
app.after(100, check_queue)
#Create tkinter app with queue that is checked regularly
app_queue = queue.Queue()
app = tk.Tk()
tk.Button(app, text='Press Me', command=button_pressed).grid()
#create_a_new_window()
#destroy_top_window()
app.after(100, check_queue)
tk.mainloop()
取消注释:
#time.sleep(1); w.destroy()
来证明销毁一个窗口两次不会产生错误消息。
我的解决方案是使用锁。在将消息发送给通知主线程创建顶层的队列之前,我获取了一个锁。在主线程创建了顶层之后,它会释放锁。
现在,在我发送消息来销毁顶层它之前,我再次获得锁,锁将阻塞,直到主线程完成创建。
import tkinter as tk
import queue
import threading
import time
def button_pressed():
threading.Thread(target=do_something_on_a_thread).start()
def do_something_on_a_thread():
global new_window
my_lock.acquire()
app_queue.put(create_a_new_window)
my_lock.acquire()
app_queue.put(new_window.destroy)
def create_a_new_window():
global new_window
new_window = tk.Toplevel()
tk.Label(new_window, text='Temporary Window').grid()
#Check queue and run any function that happens to be in the queue
def check_queue():
while not app_queue.empty():
queue_item = app_queue.get()
queue_item()
my_lock.release()
app.after(100, check_queue)
#Create tkinter app with queue that is checked regularly
app_queue = queue.Queue()
my_lock = threading.Lock()
app = tk.Tk()
tk.Button(app, text='Press Me', command=button_pressed).grid()
create_a_new_window()
new_window.destroy()
app.after(100, check_queue)
tk.mainloop()
我想到的另一个(可能更简单)解决方案是在按下按钮后在主线程上创建窗口,这将阻止线程启动,直到创建窗口:
import tkinter as tk
import queue
import threading
import time
def button_pressed():
create_a_new_window()
threading.Thread(target=do_something_on_a_thread).start()
def do_something_on_a_thread():
global new_window
app_queue.put(new_window.destroy)
def create_a_new_window():
global new_window
new_window = tk.Toplevel()
tk.Label(new_window, text='Temporary Window').grid()
#Check queue and run any function that happens to be in the queue
def check_queue():
while not app_queue.empty():
queue_item = app_queue.get()
queue_item()
app.after(100, check_queue)
#Create tkinter app with queue that is checked regularly
app_queue = queue.Queue()
app = tk.Tk()
tk.Button(app, text='Press Me', command=button_pressed).grid()
create_a_new_window()
new_window.destroy()
app.after(100, check_queue)
tk.mainloop()