Hilos y tkinter

He escuchado que los hilos en Python no son fáciles de manejar y se enredan más con tkinter.

Tengo el siguiente problema. Tengo dos clases, una para la GUI y otra para un proceso infinito. Primero, comienzo la clase GUI y luego la clase del proceso infinito. Quiero que cuando cierre la GUI, también termine el proceso infinito y el progtwig finalice.

Una versión simplificada del código es la siguiente:

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

Cuando hago clic en el botón de cerrar (en la esquina superior derecha), aparece el siguiente error en la consola:

 Tcl_AsyncDelete: async handler deleted by the wrong thread 

No sé por qué sucede o qué significa.

Todos los comandos Tcl deben originarse en el mismo hilo . Debido a la dependencia de tkinter de Tcl, generalmente es necesario hacer que todas las declaraciones de gui de tkinter originen en el mismo hilo. El problema se produce porque mainWindow se mainWindow una instancia en el subproceso tkinterGui , pero, debido a que mainWindow es un atributo de tkinterGui , no se destruye hasta que tkinterGui se destruye en el subproceso principal.

El problema se puede evitar al no convertir mainWindow un atributo de tkinterGui , es decir, cambiar self.mainWindow a mainWindow . Esto permite mainWindow cuando el método de run finaliza en el hilo tkinterGui . Sin embargo, a menudo puede evitar los subprocesos por completo usando mainWindow.after llamadas mainWindow.after lugar:

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

Si desea definir la GUI dentro de una clase, aún puede hacerlo:

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

o incluso más simple, simplemente use el hilo principal para ejecutar el 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 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() 

La solución aquí es simple, pero difícil de descubrir:

Llame a mainWindow.quit() inmediatamente después de mainwindow.mainloop() , de modo que la limpieza se mainwindow.mainloop() en el mismo hilo que el que creó la interfaz de usuario tk, en lugar de en el hilo principal cuando Python se cierra.