2016-11-02 9 views
3

これは私がここで質問した最も複雑な質問かもしれません。私は私の問題を再現することができると思う最もシンプルな私のコードを取得する時間を費やしました。何か助けを得るのがあまりに複雑でないことを願っています...destroy()キューからトップレベルのtkinterがサイレントに失敗する(競合状態?)

基本的に以下のコードでは、単一のボタンを持つtkinterアプリケーションが作成され、100msごとにキューをチェックして、別のスレッドと対話する必要があります後で。新しいウィンドウも作成され、後でエラーが発生するので非常に素早く破棄します。(これは重要かもしれません)

ボタンをクリックすると、メインスレッドに(スレッドを介して)潜在的に時間のかかるものが起こっていることを示すために使用されるウィンドウを作成すると、ウィンドウが破棄されるようにメインスレッドに通知します。

問題は、時間のかかるタスクが非常に短く、エラーもなくても、スレッドのプロセスが長い時間(たとえば秒)を要した場合、期待どおりに動作してもウィンドウが破壊されないということです。

"新しいウィンドウオブジェクトは作成されておらず、まだnew_windowに割り当てられていますか?それで、私がキューにdestroyメソッドを追加すると、実際には古い(以前に破棄された)メソッドをキューに追加します "。それは私がアプリケーションを初期化するときに私がウィンドウを作成し、破壊しなければ私がボタンをクリックするとエラーが発生する理由を説明するだろうが、なぜ私はエラーが発生しないのか説明していない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() 
+1

ところで、これはよく書かれた質問です。あなたのコードはうまく書かれていて、素晴らしいMCVEで書かれていれば... :) –

答えて

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() 
1

をいただければ幸いあなたの理論は、私にはいいですね。... Toplevelを破壊しました以前に破棄された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() 

二回窓を破壊すると、エラーメッセージを生成しないことを実証します。

+0

あなたのコードはうまくいきますが、それはリストを扱う必要があるため競合状態が起こりにくいからです2番目のスレッドでは、メインスレッドにトップレベルを作成するのに十分な時間を与えます。私はあなたのコードのいくつかのブロックが、作成が完了する前に起こってからの破壊を防ぐことができませんでしたか? – Grezzo