Tcl_AsyncDelete Error Multithreading Python

2020-04-17 02:21发布

问题:

I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.

I have the following problem. I have two classes, one for the GUI and another for an infinite process (I MUST use classes for both). First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.

A simplified version of the code is the following:

import time, threading
from tkinter import *
from tkinter import messagebox

class Interface(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.attrib1 = "Attrib from Interface class"

    def run(self): 
        #Main Window
        self.mainWindow = Tk()
        self.mainWindow.geometry("200x200")
        self.mainWindow.title("My GUI Title")
        self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit)
        #Label
        lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
        #Start
        self.mainWindow.mainloop()

    #The Interface class contains methods that use attributes from itself and attributes from Process class.
    def method1(self): 
        print(self.attrib1)
        print(SecondThread.attrib2)

    def quit(self):
        if messagebox.askyesno('App','Are you sure you want to quit?'):
            #In order to use quit function, mainWindow MUST BE an attribute of Interface. 
            self.mainWindow.destroy()
            self.mainWindow.quit()  

class Process(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.attrib2 = "Attrib from Process class"

    def run(self):
        global finish
        while not finish:
            print("Proceso infinito")
            #Inside the infinite process a method from Interface class is used.
            GUI.method1()
            time.sleep(3)

finish = False  
#Starts the GUI
GUI = Interface()
GUI.start()
#Starts the infinity process
SecondThread = Process()
SecondThread.start()    
#Waits until GUI is closed
GUI.join()
print("When GUI is closed this message appears")
#When GUI is closed we set finish to True, so SecondThread will be closed.
finish = True
#After all the program should finish but it raises the error: Tcl_AsyncDelete: async handler deleted by the wrong thread

I would appreciate your help!

回答1:

This occurs because you created the Tk main window on a thread and you don't have the UI running on the processes main thread. When you exit the process the cleanup is being done from the process primary thread. The simplest solution for your example here is to create the UI on the primary thread (the processes default thread) and only use another thread for the worker task. If your real application cannot create the UI on the primary thread you will need to look into terminating Tk from its own thread. Deleting the Tcl interpreter might do that for you.

I modified the example code to show that keeping the UI on the main thread avoids this error message. As you want your worker to be created after the UI is created but before it is running we can use the Tk after method to start the worker once the Tk mainloop is running.

import time, threading
from tkinter import *
from tkinter import messagebox

class Interface:
    def __init__(self):
        #threading.Thread.__init__(self)
        self.attrib1 = "Attrib from Interface class"
        #Main Window
        self.mainWindow = Tk()
        self.mainWindow.geometry("200x200")
        self.mainWindow.title("My GUI Title")
        self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit)
        #Label
        lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)

    #def run(self): 

    def start(self): #Start
        self.mainWindow.mainloop()

    #The Interface class contains methods that use attributes from itself and attributes from Process class.
    def method1(self): 
        print(self.attrib1)
        print(SecondThread.attrib2)

    def quit(self):
        if messagebox.askyesno('App','Are you sure you want to quit?'):
            #In order to use quit function, mainWindow MUST BE an attribute of Interface. 
            self.mainWindow.destroy()
            self.mainWindow.quit()  

class Process(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.attrib2 = "Attrib from Process class"

    def run(self):
        global finish
        while not finish:
            print("Proceso infinito")
            #Inside the infinite process a method from Interface class is used.
            GUI.method1()
            time.sleep(3)

finish = False
#Starts the GUI

GUI = Interface()
#Starts the infinity process
SecondThread = Process()
GUI.mainWindow.after(50, SecondThread.start)   
#Waits until GUI is closed
GUI.start()
#GUI.join()
print("When GUI is closed this message appears")
#When GUI is closed we set finish to True, so SecondThread will be closed.
finish = True
#After all the program should finish but it raises the error: Tcl_AsyncDelete: async handler deleted by the wrong thread