Destroy()从队列中获取top - level静默失败(竞争条件?)



这可能是我在这里问过的最复杂的问题了。我花了一些时间让我的代码变得最简单,我认为我可以重现我的问题。我希望得到任何帮助不会太复杂。

基本上在下面的代码中,创建了一个带有单个按钮的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()

最新更新