tkinterを別スレッドで起動した際、プログラムを正常終了させるのに苦労したので情報共有します。
tkinterはmainloop()
を呼び出すことで、描画処理やイベント処理をループします。
tkinterのdestroy()
を呼び出すことで、mainloop()
を終了させることができます。
その後、スレッド終了する際にtkinterの破棄処理も行われます。
ただし、別スレッドから destroy()
を呼び出したり、tkinterウィジェット(GUI部品)がインスタンス変数に代入されたままになっていたりすると、プログラムを正常終了することができませんでした。
サンプルプログラムで説明します。
import time
import threading
import tkinter as tk
class GUI:
def start(self):
self.running = True
self.window = tk.Tk()
self.window.title("Sample")
self.window.geometry('320x240')
self.value = tk.StringVar()
entry = tk.Entry(textvariable=self.value)
entry.pack()
self.window.after(1000, self._check_to_quit)
self.window.mainloop()
# need to delete variables that reference tkinter objects in the thread
del self.value
del self.window
def _check_to_quit(self):
if self.running:
self.window.after(1000, self._check_to_quit)
else:
self.window.destroy()
def quit(self):
self.running = False
def main():
gui = GUI()
thread = threading.Thread(target=gui.start)
thread.start()
time.sleep(2)
for i in 1, 2, 3, 'ダー!!':
gui.value.set(i)
time.sleep(1)
gui.quit()
thread.join()
if __name__ == '__main__':
main()
main()
で、guiとサブスレッドを生成し、サブスレッドでgui.start()
を起動しています。
main()
処理を終了する際、gui.quit()
を呼び出して終了フラグをセットし、サブスレッドが1秒間隔でチェックして、サブスレッド自らがdestroy()
を呼び出すようにしています。
この gui.quit()
の代わりに、直接 gui.window.destroy()
を呼び出すと無限ループに陥りました。
CTRL+C
もきかず、CTRL+\
で強制終了させる(コアダンプさせる)しかありませんでした。
サブスレッドでは、mainloop()
から抜けてきた後、インスタンス変数を del
しています。
この del
処理を行わないと、プログラムが異常終了(コアダンプ)したり、エラーメッセージを延々と出力し続けたりしました。
main()
から直接gui.window.destroy()
したり、インスタンス変数のdel処理をコメントアウトしたりして、何がおきるか実験してみてください。
tkinterとスレッドを併用する際には注意しましょう。