Темы и tkinter python 3

Я слышал, что потоки в Python нелегко обрабатывать, и они становятся более запутанными с tkinter.

У меня есть следующая проблема. У меня есть два класса: один для графического интерфейса и другой для бесконечного процесса. Во-первых, я запускаю класс 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 удален из-за неправильного потока

Я не знаю, почему это происходит или что это значит, пожалуйста, помогите!

Ответ 1

Все команды Tcl должны происходить из одного потока. Из-за tkinter 's зависимости от 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 mainloop:

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

Ответ 2

Исправление здесь прост, но трудно обнаружить:

Вызовите mainWindow.quit() сразу после mainwindow.mainloop(), чтобы очистка происходила в том же потоке, что и тот, который создал пользовательский интерфейс tk, а не в основном потоке при выходе из python.