python - Hilos y tkinter
python-3.x multithreading (2)
La solución aquí es simple, pero difícil de descubrir:
Llame a
mainWindow.quit()
inmediatamente después de
mainwindow.mainloop()
, para que la limpieza se
mainwindow.mainloop()
en el mismo subproceso que creó la interfaz de usuario tk, en lugar de en el subproceso principal cuando sale Python.
He oído 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 de proceso infinito. Quiero que cuando cierre la GUI, también finalice el proceso infinito y finalice el programa.
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 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 desde el mismo hilo
.
Debido a la dependencia de
tkinter
de Tcl, generalmente es necesario hacer que todas las
tkinter
gui se originen en el mismo hilo.
El problema se produce porque
mainWindow
se 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 hacer
mainWindow
un atributo de
tkinterGui
, es decir, cambiar
self.mainWindow
a
mainWindow
.
Esto permite que
mainWindow
se destruya cuando el método de
run
finalice
en el hilo
tkinterGui
.
Sin embargo, a menudo puede evitar los hilos por completo utilizando
mainWindow.after
llama en su 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 bucle principal de la 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()