What is the best way to stop a thread and avoid 'RuntimeError' in python using threading and tkinter modules? What is the best way to stop a thread and avoid 'RuntimeError' in python using threading and tkinter modules? tkinter tkinter

What is the best way to stop a thread and avoid 'RuntimeError' in python using threading and tkinter modules?


It is possible to call modifications on tkinter widgets from other threads, and they will occur as soon as the main thread is available, which may be immediately. If the background thread calling the modifications sleeps while the main thread is only in mainloop, we can simulate a pause in the app without blocking on the main thread as the question aims for.

Then we can subclass Thread to produce a thread that runs its own loop and remains started even after its target finishes so that we can call its target as many times as we like. We can even pass errors that occur on the background thread through and gracefully exit the thread without hanging the app by using daemon mode and a try-except block.

The BooleanVar thread.do acts as a switch that we can set in a lambda to run func once on the thread when button is pressed. This implements a cheap messaging system between the main and background threads which we could extend with little extra code to allow calling func with arguments and returning values from it.

import threading, time, tkinter, sysclass ImmortalThread(threading.Thread):  def __init__(self, func):    super().__init__(daemon=True)    self.func = func    self.do = tkinter.BooleanVar()  def run(self):    while True:      if self.do.get():        try:          self.func()          self.do.set(False)        except:          print("Exception on", self, ":", sys.exc_info())          raise SystemExit()      else:        # keeps the thread running no-ops so it doesn't strip        time.sleep(0.01)def func():  entry.config(state='disabled')  label.configure(text="Standby for  seconds")  time.sleep(3)  sum = 0  for i in range(int(entry.get())):    time.sleep(0.5)    sum = sum+i    label.configure(text=str(sum))  entry.config(state='normal')mainwindow = tkinter.Tk()mainwindow.title("Sum up to any number")entry = tkinter.Entry(mainwindow)entry.pack()label = tkinter.Label(mainwindow, text="Enter an integer", font=("Arial", 33))label.pack()thread = ImmortalThread(func)thread.start()button = tkinter.Button(mainwindow, text="Press me", command=lambda: thread.do.set(True))button.pack()mainwindow.mainloop()


While this is a simple way that get things done, in a fewer lines of code. I couldn't use the .join() method. But, it seems that the app isn't creating any new threads. This is obvious through threading.active_counts() method. Here is the code below:

import tkinter, threading, timedef calc():    entry.config(state='disabled')    label.configure(text="Standby for 3 seconds")    time.sleep(3)    sum = 0    for i in range(int(entry.get())):        time.sleep(0.5)        sum = sum+i        labelnum.configure(text=str(sum))    button.config(state='normal')    label.configure(text="Sum up to any number")    entry.config(state='normal')def func():    t = threading.Thread(target=calc)    t.start()    #del t    print('Active threads:',threading.active_count())mainwindow = tkinter.Tk()mainwindow.title('Sum up to any number')entry = tkinter.Entry(mainwindow)entry.pack()label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))label.pack()labelnum = tkinter.Label(mainwindow, text="",font=('Arial',33))labelnum.pack()button = tkinter.Button(mainwindow, text="Press me", command=func)button.pack()mainwindow.mainloop()