Search code examples
pythonpython-3.xtkinterpython-multithreading

threading.timer does not get closed


My code is similar to below:

from tkinter import *
from threading import Timer

root = Tk()
###
def print_data():
    Timer(1.0, lambda: print_data()).start()
    data.set('sdsds')
###
data = StringVar()
data_label = Label(root, bg = '#FFF', textvariable = data)
data_label.pack()

timer = Timer(1.0, lambda: print_data());timer.start()
root.mainloop()

This raises RuntimeError: main thread is not in main loop.

What I tried:

root.protocol("WM_DELETE_WINDOW", lambda: timer.cancel())          (close button will not work)

What is wrong?


Solution

  • tkinter doesn't support multithreading in the sense that only one thread in a multithreaded application can use it. Your timer thread breaks that rule and attempts to change the value of the StringVar named data while the mainloop() is running in the main thread.

    I don't understand everything your code is trying to accomplish, but the following shows how to do something very similar and avoids the RuntimeError by periodically polling a global flag every 500 ms to see if the print_data() function has ran.

    import tkinter as tk  # PEP 8 preferred style.
    from threading import Timer
    
    def print_data():
        global printed
        global timer
    
        printed = True
        timer = Timer(1.0, print_data)
        timer.start()
    
    def check_printed():
        global printed
    
        if printed:
            data.set('sdsds')
            printed = False
        root.after(500, check_printed)  # Check again in 500 ms.
    
    
    root = tk.Tk()
    
    data = tk.StringVar()
    data_label = tk.Label(root, bg='#FFF', textvariable=data)
    data_label.pack()
    
    timer = Timer(1.0, print_data)
    timer.start()
    
    printed = False
    check_printed()  # Start polling flag.
    
    def shutdown():
        """Orderly app shutdown."""
        if timer.is_alive():
            timer.cancel()
        root.destroy()
    
    root.protocol("WM_DELETE_WINDOW", shutdown)
    root.mainloop()
    
    print('fini')