I have went through multiple solutions on the net, but they require a lot of code that might get confusing once you scale up. Is there a simple way to stop the thread and avoid the RuntimeError: threads can only be started once
, in order to call the thread an infinite number of times. Here is a simple version of my code:
import tkinter
import time
import threading
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()
print(entry.get())
button = tkinter.Button(mainwindow, text="Press me", command=threading.Thread(target=func).start)
button.pack()
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, sys
class 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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With