I want to start a task, which should run every 10 seconds, from a GUI made in tkinter. The GUI has a start button and a stop button, with an event to stop the thread. I am using the schedule module and threading to avoid freezing the GUI.
from functools import partial
import tkinter as tk
from threading import Thread, Event, Lock
import schedule
import time
import os
class GUI():
def __init__(self,master):
self.master = master
self.frame = tk.Frame(self.master)
self.button1 = tk.Button(text='Start', command=partial(run_threaded,experiment), width=20,height=2,bg="gray",fg="black")
self.button1.pack()
self.button2 = tk.Button(text='Stop', command=stop_button, width=20,height=2,bg="gray",fg="black")
self.button2.pack()
def job():
print("Task started")
print("Task completed")
def experiment():
schedule.every(10).seconds.do(job)
while 1:
schedule.run_pending()
def run_threaded(job_func):
job_thread = Thread(target=job_func)
job_thread.start()
if stop_threads.is_set():
job_thread.join()
job_thread = None
def start_button():
run_threaded(experiment)
def stop_button():
global stop_threads
stop_threads.set()
def main():
global stop_threads
thread1=None
stop_threads = Event()
root = tk.Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
The task starts after 10 seconds and repeats periodically, but the stop button does not work and I would like to have the process start immediately, without the initial delay.
Furthermore, every other time I run it, the task starts but the GUI does not appear.
Is there a better way to accomplish this?
Try something like this:
import tkinter as tk
from time import sleep
from functools import partial
from threading import Thread
running_job = False
def job():
print("Task started")
print("Task completed")
def experiment():
while running_job:
job()
# I changed it to 1 sec to make it easier to test
sleep(1)
class GUI:
def __init__(self,master):
self.master = master
self.frame = tk.Frame(self.master)
command = partial(run_threaded, experiment)
self.button1 = tk.Button(text="Start", command=command)
self.button1.pack()
self.button2 = tk.Button(text="Stop", command=stop_button)
self.button2.pack()
def run_threaded(job_func):
global running_job
running_job = True
job_thread = Thread(target=job_func, daemon=True)
job_thread.start()
def stop_button():
global running_job
running_job = False
if __name__ == "__main__":
root = tk.Tk()
app = GUI(root)
root.mainloop()
It uses a global variable named running_job
. When running_job
is True
the while
loop inside experiment
can run. When it turns to False
, experiment
stops. The problem with this approach is that if you use tkinter
widgets/variables inside your job
function, tkinter may crash.
Another approach would be using .after
scripts instead but for that to work the job
function shouldn't take long to execute.