Threads and tkinter

15,478

All Tcl commands need to originate from the same thread. Due to tkinter's dependence on Tcl, it's generally necessary to make all tkinter gui statements originate from the same thread. The problem occurs because mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.

The problem can be avoided by not making mainWindow an attribute of tkinterGui -- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:

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

If you want to define the GUI inside a class, you can still do so:

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

or even simpler, just use the main thread to run the 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()
Share:
15,478

Related videos on Youtube

Martin DLF
Author by

Martin DLF

Updated on June 01, 2022

Comments

  • Martin DLF
    Martin DLF almost 2 years

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

    When I click in the close button (in the upper right corner) the following error appears in the console:

    Tcl_AsyncDelete: async handler deleted by the wrong thread
    

    I don't know why it happens or what it means.

    • mguijarr
      mguijarr over 9 years
      Your simplified version works ok for me... There must be something you forgot to add that is causing your problem
    • Martin DLF
      Martin DLF over 9 years
      @mguijarr I read in google that this error is more common in Window, what's your SO? Mine is Windows 7 x64. Maybe windows is the problem :/
  • Martin DLF
    Martin DLF over 9 years
    Thanks you a lot! You are a master!
  • Martin DLF
    Martin DLF over 9 years
    But sometimes you NEED to make mainWindow as an attribute, for example, if you want to use: self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit) When you define the function quit() and you write: self.mainWindow.destroy() self.mainWindow.quit() mainWindow must be an attribute, otherwise the function quit won't recognize mainWindow.
  • unutbu
    unutbu over 9 years
    You can still use a class; just don't make the instance an attribute of the threading.Thread. I've added some code above to suggest how.
  • Donal Fellows
    Donal Fellows over 9 years
    Strictly, Tcl can do multithreading and Tk can too (provided they're built with the right options) but you've got to keep an entire context per thread. The threading model is totally different to that of Python — it's much more like having a separate OS process — though that does mean that Tcl uses global locks far more sparingly.
  • Russell Smith
    Russell Smith about 7 years
    this doesn't seem to be a universal solution. I have code exhibiting this same problem (Tcl_AsyncDelete error) and adding mainWindow.quit() after mainWindow.mainloop() has no effect.
  • jez
    jez over 6 years
    @BryanOakley yep, same issue: in trying to avoid Tcl_AsyncDelete I have found .quit() unnecessary on Python 2.7.14, and ineffective on Python 3.6.3. Haven't got a solution yet.