Python, Tkinter: how can I prevent tkinter gui mainloop crash using threading - python

Hi I have a small python gui interface with two buttons, start(That starts a counter) and stop (that is suppose to stop the counter), the counter is an infinite loop since I do not want it to end unless the second button is clicked. The problem is the second button cannot be clicked while the function from the first one is still running.
I read that I need to use threading and I have tried but I do not fully understand how I can do this. Please help.
from Tkinter import *
import threading
class Threader(threading.Thread):
def run(self):
for _ in range(10):
print threading.current_thread().getName()
def main(self):
import itertools
for i in itertools.count(1, 1):
print i
def other(self):
print "Other"
m = Threader(name="main")
o = Threader(name="other")
try:
'''From here on we are building the Gui'''
root = Tk()
'''Lets build the GUI'''
'''We need two frames to help sort shit, a left and a right vertical frame'''
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
'''Widgets'''
'''Buttons'''
playButton = Button(leftFrame, text="Play", fg="blue", command=m.main)
stopButton = Button(rightFrame, text="Stop", fg="red", command=o.other)
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()
except Exception, e:
print e

Here's a short example of using threading. I took out your other function and I don't know why your using itertools here. I took that out as well and simply setup using a simple threading example.
A few things:
You setup using threading.Thread as the base class for Threader, but you never actually initialized the base class.
Whenever you use threading you generally want to define a run method and then use start() to start the thread. Calling start() will call run.
You need to use threading to prevent your GUI blocking, because tkinter is just one thread on a giant loop. So, whenever you have some long running process it blocks this thread until the current process is complete. That's why it's put in another thread. Python has something called the GIL, which prevent's true parallelization (I made up that word) since it only one thread can ever be used at a time. Instead, it uses time slicing, the GIL sort of "polls" between them to give the appearance of multiple tasks running concurrently. For true parallel processing you should use multiprocessing.
In the below code I have used self.daemon = True. Setting the thread to be a daemon will kill it when you exit the main program (In this case the Tk GUI)
from tkinter import *
import threading, time
class Threader(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.daemon = True
self.start()
def run(self):
while True:
print("Look a while true loop that doesn't block the GUI!")
print("Current Thread: %s" % self.name)
time.sleep(1)
if __name__ == '__main__':
root = Tk()
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
playButton = Button(leftFrame, text="Play", fg="blue",
command= lambda: Threader(name='Play-Thread'))
stopButton = Button(rightFrame, text="Stop", fg="red",
command= lambda: Threader(name='Stop-Thread'))
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()

For something as simple as a counter, Tkinter's after() method is usually a better choice. You can use an instance variable to set it to on and off.
class TimerTest():
def __init__(self, root):
self.root=root
Button(root, text="Play", fg="blue",
command=self.startit).grid(row=1, column=0)
Button(root, text="Stop", fg="red",
command=self.stopit).grid(row=1, column=1)
self.is_running=True
self.count=IntVar()
Label(root, textvariable=self.count,
bg="lightblue").grid(row=0, column=0, columnspan=2, sticky="ew")
def startit(self):
self.is_running=True
self.increment_counter()
def increment_counter(self):
if self.is_running:
c=self.count.get()
c += 1
self.count.set(c)
root.after(1000, self.increment_counter) ## every second
def stopit(self):
self.is_running = False
root = Tk()
TT=TimerTest(root)
root.mainloop()

Related

Update Tkinter label during threading in Python

I am currently trying to program a robot arm which is controlled by a Raspberry Pi.
Everything works fine so far, except for one thing and I already googled and tried everything for many hours but can't find a working solution.
For the movement of the robot arm it is necessary to run all motors "simultaneously" with threads (works fine).
The problem I have is that I need to update a label which shows the current angle of an axis (motor) as soon as it finished its movement but other motors are still running (threads).
After a lot of research I thought I found the solution by using a queue and Tkinters after-method. But it still doesn't work as the labels text only gets updated after all threads terminated.
I wrote an example code where I want to get a label update for motor "one" which will finish its for-loop (100 iterations) before motor "two" (500 iterations). I expected the label to get updated as soon as motor one reached its target while motor two is still runing.
But although I used the after-method it still waits till motor two finished before updating the label.
Hope you can help me!
from tkinter import *
import threading
import time
from queue import *
class StepperMotors:
def __init__(self, root):
self.root = root
self.start_btn = Button(root, text="Start", command=lambda:self.start_movement())
self.start_btn.config(width = 10)
self.start_btn.grid(row=1,column=1)
self.label_one = Label(root, text='')
self.label_one.config(width = 10)
self.label_one.grid(row=2, column=1)
self.label_two = Label(root, text='')
self.label_two.config(width = 10)
self.label_two.grid(row=3, column=1)
def start_movement(self):
self.thread_queue = Queue()
self.root.after(100, self.wait_for_finish)
thread_one = threading.Thread(target=self.motor_actuation, args=(1,100))
thread_two = threading.Thread(target=self.motor_actuation, args=(2,500))
thread_one.start()
thread_two.start()
thread_one.join()
thread_two.join()
def motor_actuation(self, motor, iterations):
for i in range(iterations):
i = i+1
update_text = str(motor) + " " + str(i) + "\n"
print(update_text)
time.sleep(0.01)
self.thread_queue.put(update_text)
def wait_for_finish(self):
try:
self.text = self.thread_queue.get()
self.label_one.config(text=self.text)
except self.thread_queue.empty():
self.root.after(100, self.wait_for_finish)
if __name__ == "__main__":
root = Tk()
root.title("test")
stepper = StepperMotors(root)
root.mainloop()
It’s better to use daemon thread which are non-blocking.
Also, It’s better to have a separation of concerns: a robot (or robot arm) can be a object which has its own life time: a daemon thread. Idem, you can define a "LabelUpdater" which read the state of a robot and update a label.
Let’s define a robot:
it is created on application initialisation and is run when the user click the "Start" button,
The robot moves and reports its angle in a app-level multithreading-queue,
class Robot(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, end_pos: int):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.end_pos = end_pos
def run(self) -> None:
for angle in range(self.end_pos):
self.label_queue.put(angle)
time.sleep(0.01)
Let’s define a LabelUpdater:
It is created on application initialisation and run forever (it can observe a robot even if it is not running).
It reads the robot queue (for instance each second to avoid blinking) and update the label
class LabelUpdater(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, root_app: tkinter.Tk, variable: tkinter.Variable):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.root_app = root_app
self.variable = variable
def run(self) -> None:
# run forever
while True:
# wait a second please
time.sleep(1)
# consume all the queue and keep only the last message
last_msg = None
while True:
try:
msg = self.label_queue.get(block=False)
except queue.Empty:
break
last_msg = msg
self.label_queue.task_done()
if last_msg:
self.variable.set(last_msg)
Then, the main application should define:
2 multithreading queues: one for each label,
2 tkinter.StringVar variable which will be updated,
the robots and the label updaters,
the two updaters are started, and will run forever.
class StepperMotors:
def __init__(self, root):
self.root = root
self.label_one_queue = queue.Queue()
self.label_two_queue = queue.Queue()
self.start_btn = tkinter.Button(root, text="Start", command=lambda: self.start_movement())
self.start_btn.config(width=10)
self.start_btn.grid(row=1, column=1)
self.text_one = tkinter.StringVar()
self.text_one.set("one")
self.label_one = tkinter.Label(root, textvariable=self.text_one)
self.label_one.config(width=10)
self.label_one.grid(row=2, column=1)
self.text_two = tkinter.StringVar()
self.text_two.set("two")
self.label_two = tkinter.Label(root, textvariable=self.text_two)
self.label_two.config(width=10)
self.label_two.grid(row=3, column=1)
self.robot_one = Robot("robot_one", self.label_one_queue, 100)
self.robot_two = Robot("robot_two", self.label_two_queue, 500)
self.updater_one = LabelUpdater("updater_one", self.label_one_queue, self.root, self.text_one)
self.updater_two = LabelUpdater("updater_two", self.label_two_queue, self.root, self.text_two)
self.updater_one.start()
self.updater_two.start()
def start_movement(self):
self.robot_one.start()
self.robot_two.start()
Of course, you need a flag or something to check that each robot is not already running.

How to stop a program with infinity loop in Python (tkinter)?

I have a problem with stopping the program. When I click exit button, the mainloop stops but program is still running. I am new to it and I have no idea what to do. I know the issue is the thread is still running and I don't know how to stop it.
This is my code:
from tkinter import *
import simulation as s
import graph as g
import numpy as np
from tkinter import filedialog
import threading
def main():
root = Tk()
root.title("Simulator")
switch = True
def get_filename():
return filedialog.askopenfilename(parent=root)
def play():
def run():
while switch:
s.simulation(s.particle, np.inf, s.initial_time_step, get_filename())
thread = threading.Thread(target=run)
thread.start()
def start_simulation():
global switch
switch = True
v.set('Simulation is running!')
play()
def stop_simulation():
global switch
v.set('Simulation is stopped!')
switch = False
def draw_graphs():
g.create_graphs()
start = Button(root, text='Start simulation', command=start_simulation, width=50)
start.pack()
finish = Button(root, text='Stop simulation', command=stop_simulation, width=50)
finish.pack()
graphs = Button(root, text='Graphs', command=draw_graphs, width=50)
graphs.pack()
exit_button = Button(root, text='Exit', command=root.destroy, width=50)
exit_button.pack()
v = StringVar()
statement = Label(root, textvariable=v)
statement.pack()
root.mainloop()
if __name__ == '__main__':
main()
Create a function for the exit button which also stops your thread
def exit():
switch = False
root.destroy()
And later:
exit_button = Button(root, text='Exit', command=exit, width=50)
You can also call the same method whenever the window is closed with the top right X button. Just bind your method to the event:
root.protocol("WM_DELETE_WINDOW", exit)
Ps: You dont need to use global here, because your nested functions have access to the outer functions variables

Starting and stopping a thread from a gui

I'm currently developing a program with a GUI to gather data from a sensor and visualise it within the GUI.
The data from the sensor is stored in a list for further calculations.
What I'm currently trying to do, is starting the logging process in a new thread.
This should stop the GUI from freezing.
My current code:
import tkinter as tk
import time
import threading
class GUI:
def __init__(self, tk_object):
self.gui = tk_object
self.gui.title("Logger")
self.gui.resizable(width=True, height=True)
self.testlabel = tk.Label(self.gui, text="Recording in process")
self.testlabel.grid(row = 7, column = 0)
self.testlabel.config(bg='white')
btn1 = tk.Button(master, text="Start Recording", width=16, height=5, command=lambda: self.start_functions())
btn1.grid(row=2,column=0)
btn2 = tk.Button(master, text="Stop Recording", width=16, height=5, command=lambda: self.stop_functions())
btn2.grid(row=3,column=0)
def start_functions(self):
"""Calls the method get_sample in a new thread and changes the bg-color of the label to red"""
Thread_1 = threading.Thread(target=self.get_sample(), name='Thread1')
Thread_1.start()
self.testlabel.config(bg='red')
def stop_functions(self):
"""Stops all active threads and changes bg-color of the label back to white"""
#stop_threads = threading.Event()
#stop_threads.set()
threading.Event().set()
self.testlabel.config(bg='white')
def get_sample(self):
"""get data and append it to a list"""
while not threading.Event().is_set():
time.sleep(1)
res_cel.append(res_cel[-1]+1)
x_value.append(x_value[-1]+1)
print(res_cel)
print(x_value)
master = tk.Tk()
master_object = GUI(master)
master.mainloop()
Currently, the get_sample method contains a placeholder.
I'm trying to stop the Thread1 (where the get_sample method is running in) via the Event() handler.
while not threading.Event().is_set():
This doesn't seem to work in a proper way.
Are there better ways to do this?
Before this approach I tried using a class to handle the threads (This was found on stackoverflow, but I can't seem to find it anymore, sorry):
class Controller(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def run(self):
.. do somehting
and starting / stopping the threads via:
def starting_thread(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.run)
self.thread1.start()
def stopping_thread(self):
self.stop_threads.set()
self.thread1 = None
Note: These functions are within the Controller Class.
With this solution I wasn't able to alter the lables background color in the GUI Class, since it doesn't know what object it is referencing to.
Im fairly new to programming python so I'd be glad if someone could explain to me what I'm missing here.
Thanks
xSaturn
You'd better create your own Thread object with a method to stop it. A thread stop running when it goes of its run method. Look at the example bellow
import time
import tkinter as tk
from threading import Thread
class MyThread(Thread):
thread_running = False
def run(self):
k = 0
self.thread_running = True
while self.thread_running:
print(k)
k +=1
time.sleep(1)
print("thread ended")
def stop(self):
self.thread_running = False
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.thread = None
tk.Button(self, text="launch thread", command=self.launch_thread)\
.grid(row=1, column=0)
tk.Button(self, text="stop thread", command=self.stop_thread)\
.grid(row=2, column=0)
def launch_thread(self):
if self.thread:
print("thread already launched")
else:
print("thread launched")
self.thread = MyThread()
self.thread.start()
def stop_thread(self):
if self.thread:
self.thread.stop()
self.thread = None
else:
print("no thread running")
if __name__ == '__main__':
win = Window()
win.mainloop()
See this topic for more information
Is there any way to kill a Thread?

Tkinter python multithread (avoid "Not Responding")

I'm trying to write a Python GUI program with tkinter.
I want to make two thread. One runing with the main_form function to keep tkinter from keep update and loop (avoid "Not Responding").
The other, when the button1 (btn1) is clicked make function sci_thread() start running and start thread2 that execute the main_scikit with long time code.
But tkinter keep Not Responding.
Below is my code:
import threading
class class_one:
def main_scikit(seft):
######
code_take_loooong_time
######
def save(seft):
pass
def main_form(seft):
root = Tk( )
root.minsize(width=300, height=500)
ent1 = Entry(width=30)
ent1.grid(row=0,column=1,padx = 10,pady=5)
bnt1 = Button(root,text = "Start",command=lambda : seft.sci_thread())
bnt1.grid(row=5,column=0,padx = 10)
root.update()
root.mainloop()
def sci_thread(seft):
maincal = threading.Thread(2,seft.main_scikit())
maincal.start()
co = class_one()
mainfz = threading.Thread(1,co.main_form());
mainfz.start()
Your app is unresponsive because your target parameter executed when declared and result of that passed as target. And, obviously, because of that you GUI is unresponsive while code_take_loooong_time being executed in GUI's thread. To deal with it - get rid of redundant parentheses.
Try this snippet:
import threading
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class class_one:
def main_scikit(self):
######
# code_take_loooong_time
# same as sleep
threading.Event().wait(5)
# some print
self.print_active_threads_count()
######
def save(self):
pass
def main_form(self):
self.root = tk.Tk()
self.root.minsize(width=300, height=500)
self.ent1 = tk.Entry(self.root, width=30)
self.ent1.grid(row=0, column=1, padx=10, pady=5)
self.bnt1 = tk.Button(self.root, text="Start", command=self.sci_thread)
self.bnt1.grid(row=5, column=0, padx=10)
self.root.update()
self.root.mainloop()
def sci_thread(self):
maincal = threading.Thread(target=self.main_scikit)
maincal.start()
def print_active_threads_count(self):
msg = 'Active threads: %d ' % threading.active_count()
self.ent1.delete(0, 'end')
self.ent1.insert(0, msg)
print(msg)
co = class_one()
mainfz = threading.Thread(target=co.main_form)
mainfz.start()
Links:
Similar problem with a tkinter button's parameter
More general: How to pass a method as an parameter
Threading docs
P.S.:
Also, be careful when you start a tkinter application not in the main thread because tkinter expects (in general) that mainloop is outer-most loop possible and that all Tcl commands invoked from the same thread. So there can be many and more synchronisation problem with all that, even if you just trying to quit GUI!
In conclusion, maybe this and that would give you some new ideas.

Python TKinter Threading Help Required

I am trying to create a GUI that communicates with a RS232 serial object. I'll present an analogous scenario to the problem that I am facing. I want to create a frame with 2 buttons, Start and Stop. The start button calls the 'foo' function:
status = True
def foo():
n = 0
while(getStatus()):
print n
n += 1
sleep(0)
This foo function keeps running until I press stop.
def getStatus():
return status
def stop():
status = False
I understand Tkinter is single-threaded and once I press 'Start', the GUI will freeze. I know this is possible with the after function, but i strictly want to use threading. Is this possible with threading? If so can you please provide a sample code? Thank you.
here is some (not yet perfect) code:
What is missing/broken, but you did not ask for this, I added links:
It does not use locks => the calls to set might brake since they can occur at the same time. read the docs (this is quite easy)
It updates the gui from another thread. see 1 2
possibly more (not a threading guru)
Also for stopping threads look here
import time
import tkinter
from tkinter import ttk
import threading
#gui
root = tkinter.Tk()
root.title("Threading demo")
status = tkinter.StringVar()
elapsed = tkinter.StringVar()
error = tkinter.StringVar()
#thread
class timer(threading.Thread):
def __init__(self):
super().__init__()
self.stopped = False
#your code here, don't need init if you have no code
def run(self):
status.set('running')
while not self.isStopped():
time.sleep(1)
try:
oldtime = int(elapsed.get())
except ValueError:
oldtime = 0
elapsed.set(oldtime+1)
status.set('stopped')
time.sleep(2)
def isStopped(self):
return self.stopped
def stop(self):
self.stopped = True
#starts/stops thread (manages it)
class threadedOp(object):
def __init__(self):
self.thread = None
def run(self):
if self.thread == None:
self.thread = timer()
status.set('starting')
self.thread.start()
else:
error.set('Thread already running')
def stop(self):
if self.thread != None:
status.set('stopping')
self.thread.stop()
self.thread.join()
error.set('Join complete')
self.thread = None
else:
error.set('No thread to stop')
op = threadedOp()
#remaining gui
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
ttk.Label(mainframe, textvariable=elapsed).grid(column=1, row=1, sticky=(tkinter.W, tkinter.E))
ttk.Label(mainframe, textvariable=status).grid(column=2, row=1, sticky=(tkinter.W, tkinter.E))
ttk.Label(mainframe, textvariable=error).grid(column=1, row=3, sticky=(tkinter.W, tkinter.E))
ttk.Button(mainframe, text="Start", command=op.run).grid(column=1, row=2, sticky=tkinter.W)
ttk.Button(mainframe, text="Stop", command=op.stop).grid(column=2, row=2, sticky=tkinter.W)
root.mainloop()

Categories