web-dev-qa-db-ja.com

スレッドとtkinter

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

なぜそれが起こるのか、それが何を意味するのかわかりません。

15
Martin DLF

すべてのTclコマンドは同じスレッドから発信する必要がありますtkinterはTclに依存しているため、通常、すべてのtkinterguiステートメントを同じスレッドから発信する必要があります。この問題は、mainWindowtkinterGuiスレッドでインスタンス化されるために発生しますが、-mainWindowtkinterGuiの属性であるため、-tkinterGuiがメインスレッドで破棄されるまで破棄されません。

この問題は、mainWindowtkinterGuiの属性にしないことで回避できます。つまり、self.mainWindowmainWindowに変更します。これにより、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()
16
unutbu

ここでの修正は簡単ですが、見つけるのは困難です。

mainWindow.quit()の直後にmainwindow.mainloop()を呼び出すと、クリーンアップがメインスレッドではなく、tkUIを作成したスレッドと同じスレッドで実行されます。 python終了します。

0
Eric