这可能是我在这里提出的最复杂的问题。我花了一些时间让我的代码变得最简单,我认为我可以重现我的问题。我希望得到任何帮助并不太复杂......
基本上在下面的代码中,创建了一个带有单个按钮的tkinter应用程序,它每100ms检查一次队列,因为不同的线程可能需要稍后与之交互。还会快速创建和销毁新窗口,因为我稍后会收到错误,否则(这可能很重要)
当单击该按钮时,会创建一个新线程,告诉主线程(通过队列)创建一个窗口,用于指示可能耗时的事情,然后当完成时,它会告诉主线程(通过队列)来破坏窗口。
问题是如果耗时的任务非常短并且没有错误,则窗口不会被销毁,但如果线程中的进程花了很长时间(例如一秒),它就会按预期工作。
我想知道它是否是“新的窗口对象尚未创建并分配给new_window
,所以当我将destroy方法添加到队列中时,我实际上是在添加旧的(之前已被破坏) )对象的破坏方法到队列“。这可以解释为什么我在第一次单击按钮时遇到错误,如果我在初始化应用程序时没有创建并销毁窗口,但它无法解释为什么我没有收到错误调用{{ 1}}以前被毁坏的destroy()
...如果我对这个理论是对的,我真的不知道解决方案是什么,所以任何想法都会受到赞赏
Toplevel
答案 0 :(得分:1)
你的理论听起来不错。在先前销毁的Toplevel窗口上,您没有收到错误或警告.destroy
因为Tkinter“有帮助”并没有抱怨。 :)
这是您的代码版本似乎有效,至少它不会留下不需要的窗口。我摆脱了那个global
,并将窗户推到一个堆栈上,这样当我想要销毁它们时我可以弹出它们。在您的真实代码中,您可能希望循环堆栈并检查窗口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()
证明两次销毁窗口不会产生错误信息。
答案 1 :(得分:1)
我的解决方案似乎有效使用锁。在我将消息发送到队列之前我获取了一个锁,该队列告诉主线程创建顶层。在主线程创建了顶层之后,它会释放锁。
现在,在我发送消息来销毁它之前,我再次获取锁定,这将阻塞直到主线程完成创建它。
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()