Pythonのスレッドは扱いが簡単ではなく、tkinterとより絡み合うようになると聞きました。
私は次の問題を抱えています。 GUI用と無限プロセス用の2つのクラスがあります。最初にGUIクラスを開始し、次に無限プロセスのクラスを開始します。 GUIを閉じると、無限のプロセスも終了し、プログラムが終了するようにしたいと思います。
コードの簡略版は次のとおりです。
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
(右上隅にある)閉じるボタンをクリックすると、コンソールに次のエラーが表示されます。
Tcl_AsyncDelete: async handler deleted by the wrong thread
なぜそれが起こるのか、それが何を意味するのかわかりません。
すべてのTclコマンドは同じスレッドから発信する必要があります 。 tkinter
はTclに依存しているため、通常、すべてのtkinter
guiステートメントを同じスレッドから発信する必要があります。この問題は、mainWindow
がtkinterGui
スレッドでインスタンス化されるために発生しますが、-mainWindow
はtkinterGui
の属性であるため、-tkinterGui
がメインスレッドで破棄されるまで破棄されません。
この問題は、mainWindow
をtkinterGui
の属性にしないことで回避できます。つまり、self.mainWindow
をmainWindow
に変更します。これにより、mainWindow
メソッドが終了したときにrun
を破棄できますtkinterGui
スレッド内。ただし、多くの場合、代わりにmainWindow.after
呼び出しを使用することで、スレッドを完全に回避できます。
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
クラス内でGUIを定義する場合でも、次のように行うことができます。
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
またはさらに簡単に、メインスレッドを使用してGUIメインループを実行します。
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
ここでの修正は簡単ですが、見つけるのは困難です。
mainWindow.quit()
の直後にmainwindow.mainloop()
を呼び出すと、クリーンアップがメインスレッドではなく、tkUIを作成したスレッドと同じスレッドで実行されます。 python終了します。