destroy() tkinter toplevel from queue 默默地失败(竞争条件?)
destroy() tkinter toplevel from queue fails silently (race condition?)
这可能是我在这里问过的最复杂的问题。我花了一些时间让我的代码尽可能简单,以重现我的问题。我希望得到任何帮助都不会太复杂...
基本上在下面的代码中,创建了一个带有单个按钮的 tkinter 应用程序,它每 100 毫秒检查一次队列,因为不同的线程稍后可能需要与之交互。一个新的 window 也很快被创建和销毁,因为我稍后会得到一个错误,否则 (这可能很重要)
单击按钮时,将创建一个新线程,告诉主线程(通过队列)创建一个 window 用于指示可能耗时的事情正在发生,然后当完成后,它告诉主线程(通过队列)销毁 window.
问题是window如果耗时很短也没有报错,但如果线程中的进程耗时较长(比如一秒),则window不会被销毁, 它按预期工作。
我想知道它是否类似于"The new window object hasn't been created and assigned to new_window
yet, so when I add the destroy method to the queue, I am actually adding the old (previously destroyed) object's destroy method to the queue"。这可以解释为什么如果我在初始化应用程序时没有创建和销毁 window ,那么我第一次单击按钮时会出现错误,但这并不能解释为什么我没有得到在之前被破坏的 Toplevel
上调用 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
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()
你的理论对我来说听起来不错。你不会在之前销毁的 Toplevel window 上调用 .destroy
时收到错误或警告,因为 Tkinter "helpfully" 不会抱怨这一点。 :)
这是您的代码的一个版本,它似乎可以工作,至少不会留下不需要的 windows。我去掉了 global
,并将 windows 压入堆栈,以便在我想销毁它们时弹出它们。在您的真实代码中,您可能希望遍历堆栈并检查 window 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()
证明销毁 window 两次不会产生错误消息。
我的解决方案似乎有效使用锁。在将消息发送到告诉主线程创建顶层的队列之前,我获得了一个锁。主线程创建顶层后,释放锁。
现在,在我发送消息销毁顶层之前,我再次获取锁,这将阻塞直到主线程完成创建它。
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()
我想出的另一个(可能更简单)解决方案是在按下按钮后在主线程上创建 window,这将阻止线程启动,直到 window 已创建:
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()
这可能是我在这里问过的最复杂的问题。我花了一些时间让我的代码尽可能简单,以重现我的问题。我希望得到任何帮助都不会太复杂...
基本上在下面的代码中,创建了一个带有单个按钮的 tkinter 应用程序,它每 100 毫秒检查一次队列,因为不同的线程稍后可能需要与之交互。一个新的 window 也很快被创建和销毁,因为我稍后会得到一个错误,否则 (这可能很重要)
单击按钮时,将创建一个新线程,告诉主线程(通过队列)创建一个 window 用于指示可能耗时的事情正在发生,然后当完成后,它告诉主线程(通过队列)销毁 window.
问题是window如果耗时很短也没有报错,但如果线程中的进程耗时较长(比如一秒),则window不会被销毁, 它按预期工作。
我想知道它是否类似于"The new window object hasn't been created and assigned to new_window
yet, so when I add the destroy method to the queue, I am actually adding the old (previously destroyed) object's destroy method to the queue"。这可以解释为什么如果我在初始化应用程序时没有创建和销毁 window ,那么我第一次单击按钮时会出现错误,但这并不能解释为什么我没有得到在之前被破坏的 Toplevel
上调用 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
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()
你的理论对我来说听起来不错。你不会在之前销毁的 Toplevel window 上调用 .destroy
时收到错误或警告,因为 Tkinter "helpfully" 不会抱怨这一点。 :)
这是您的代码的一个版本,它似乎可以工作,至少不会留下不需要的 windows。我去掉了 global
,并将 windows 压入堆栈,以便在我想销毁它们时弹出它们。在您的真实代码中,您可能希望遍历堆栈并检查 window 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()
证明销毁 window 两次不会产生错误消息。
我的解决方案似乎有效使用锁。在将消息发送到告诉主线程创建顶层的队列之前,我获得了一个锁。主线程创建顶层后,释放锁。
现在,在我发送消息销毁顶层之前,我再次获取锁,这将阻塞直到主线程完成创建它。
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()
我想出的另一个(可能更简单)解决方案是在按下按钮后在主线程上创建 window,这将阻止线程启动,直到 window 已创建:
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()