tKinter Multithreading Stopping a Thread - python

I have created a Python GUI with tKinter in my simple example I have a button that triggers a simple loop that increments a counter. I have succesfully threaded the counter so my GUI won't freeze, however I am having issues with getting it to stop counting. Here is my code:
# threading_example.py
import threading
from threading import Event
import time
from tkinter import Tk, Button
root = Tk()
class Control(object):
def __init__(self):
self.my_thread = None
self.stopThread = False
def just_wait(self):
while not self.stopThread:
for i in range(10000):
time.sleep(1)
print(i)
def button_callback(self):
self.my_thread = threading.Thread(target=self.just_wait)
self.my_thread.start()
def button_callbackStop(self):
self.stopThread = True
self.my_thread.join()
self.my_thread = None
control = Control()
button = Button(root, text='Run long thread.', command=control.button_callback)
button.pack()
button2 = Button(root, text='stop long thread.', command=control.button_callbackStop)
button2.pack()
root.mainloop()
How can I safely make the counter stop incrementing and gracefully close the thread?

You have to check for self.stopThread inside the for loop

So you want a for loop AND a while loop to run in parallel? Well they can't. As you have them, the for loop is running and won't pay attention to the while loop condition.
You need to make only a single loop. If you want your thread to auto terminate after 10000 cycles, you could do it like this:
def just_wait(self):
for i in range(10000):
if self.stopThread:
break # early termination
time.sleep(1)
print(i)

Related

Tkinter crash in python

I was trying to make a stopwatch in python but every time it stops working beacause of overflow, can someone please fix this??
Code:
import time
from tkinter import *
cur=time.time()
root = Tk()
def functio():
while True:
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
time.sleep(0.5)
Button(root,text='Start',command=functio).pack()
root.mainloop()
The while loop will block tkinter mainloop from handling pending events, use after() instead.
Also it is better to create the label once outside the function and update it inside the function:
import time
# avoid using wildcard import
import tkinter as tk
cur = time.time()
root = tk.Tk()
def functio():
# update label text
l1['text'] = round(time.time()-cur, 4)
# use after() instead of while loop and time.sleep()
l1.after(500, functio)
tk.Button(root, text='Start', command=functio).pack()
# create the label first
l1 = tk.Label(root)
l1.pack()
root.mainloop()
Note that wildcard import is not recommended.
Flow of execution can never exit your endless while-loop. It will endlessly block the UI, since flow of execution can never return to tkinter's main loop.
You'll want to change your "functio" function to:
def functio():
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
root.after(500, functio)
That being said, I'm not sure this function makes much sense: You create a widget, and then immediately destroy it?
You'll want to do something like this instead:
import time
from tkinter import *
root = Tk()
def functio():
global timerStarted
global cur
# check if we started the timer already and
# start it if we didn't
if not timerStarted:
cur = time.time()
timerStarted = True
s = round(time.time()-cur, 1)
# config text of the label
l1.config(text=s)
# use after() instead of sleep() like Paul already noted
root.after(500, functio)
timerStarted = False
Button(root, text='Start', command=functio).pack()
l1 = Label(root, text='0')
l1.pack()
root.mainloop()

Tkinter Buttons and Multithreading?

Off Button Not working in python. I have a code in python in which I'm trying to get the off button to turn a function off while the function on is running. as on runs it prints out hello every 5 seconds and I want the off button to stop this. Every time I press the off button it freezes while hello is still being played or python crashes. How can I fix this?.
Here is the code I have so far:
from tkinter import *
import threading
root = Tk()
def on():
threading.Timer(5.0, on).start()
print("hello")
def off():
exit()
buttonstart = Button(root, text="on", command=on)
button = Button(root, text="off", command=off)
buttonstart.pack()
button.pack()
root.mainloop()
You can use cancel() to cancel the Timer():
timer_task = None
def on():
global timer_task
buttonstart.config(state=DISABLED)
timer_task = threading.Timer(5.0, on)
timer_task.start()
print('hello')
def off():
global timer_task
if timer_task:
timer_task.cancel()
buttonstart.config(state=NORMAL)
timer_task = None

Using Tkinter button to stop an iterating function

I have created a GUI with a "stop" button. When the GUI is ran, another module is called that contains a while loop for a background function. The stop button would be used to pass a variable to the loop to stop it. However, when the module containing the loop is called the GUI freezes. I have considered using the library "threading" but cannot find any tkinter specific content. Any advice or small example of how you would create the code would help a lot.
Here is a basic GUI with 2 buttons that can start and stop a thread which increments a variable count.
I let you try it :
import tkinter as tk
import threading
import time
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("test")
self.button_start = tk.Button(self, text="Start", command=self.start_thread)
self.button_start.pack()
self.button_stop = tk.Button(self, text="Stop", command=self.stop_thread)
self.button_stop.pack()
self.count = 0
self.continue_thread = True
def start_thread(self):
self.count = 0
self.continue_thread = True
self.t1 = threading.Thread(target = self.counter)
self.t1.daemon = True # With this parameter, the thread functions stops when you stop the main program
self.t1.start()
def stop_thread(self):
self.continue_thread = False
self.t1.join()
def counter (self):
while self.continue_thread:
print("i =", self.count)
self.count += 1
time.sleep(1)
if __name__ == "__main__":
app = GUI()
app.mainloop()
This is just a sample program to illustrate how to kill a running thread.
import threading
import time
def run():
while True:
print('thread running')
global stop_threads
if stop_threads:
break
if __name__=="__main__":
stop_threads = False
t1 = threading.Thread(target = run) ##background loop
t1.start()
time.sleep(1)
#while clicking on the button in GUI kill the thread like this
stop_threads = True
t1.join()
print('thread killed')

How to connect a progress bar to a function?

I'm trying to connect a progress bar to a function for my project.
This is what I have so far but im pretty sure it does nothing:
def main():
pgBar.start()
function1()
function2()
function3()
function4()
pgBar.stop()
Here is the code where I make my progress bar if that helps at all:
pgBar = ttk.Progressbar(window, orient = HORIZONTAL, length=300, mode = "determinate")
pgBar.place(x=45, y=130)
I have been doing some research and understand that the tkinter window freezes when running a function or something like that. Is there a way I could "unfreeze" the window at the end of each function that is called inside the main one?
Since tkinter is single threaded, you need another thread to execute your main function without freezing the GUI. One common approach is that the working thread puts the messages into a synchronized object (like a Queue), and the GUI part consumes this messages, updating the progress bar.
The following code is based on a full detailed example on ActiveState:
import tkinter as tk
from tkinter import ttk
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.listbox = tk.Listbox(self, width=20, height=5)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300, mode='determinate')
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.listbox.pack(padx=10, pady=10)
self.progressbar.pack(padx=10, pady=10)
self.button.pack(padx=10, pady=10)
def spawnthread(self):
self.button.config(state="disabled")
self.thread = ThreadedClient(self.queue)
self.thread.start()
self.periodiccall()
def periodiccall(self):
self.checkqueue()
if self.thread.is_alive():
self.after(100, self.periodiccall)
else:
self.button.config(state="active")
def checkqueue(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.listbox.insert('end', msg)
self.progressbar.step(25)
except Queue.Empty:
pass
class ThreadedClient(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 5):
time.sleep(2)
msg = "Function %s finished..." % x
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
Since the original example on ActiveState is a bit messy IMO (the ThreadedClient is quite coupled with the GuiPart, and things like controlling the moment to spawn the thread from the GUI are not as straightforward as they could be), I have refactored it and also added a Button to start the new thread.
To understand the 'freezing' you need to understand mainloop(). Calling this method starts the tkinter event loop. The main thread is responsible for this loop. Therefore, when your work intensive function runs in the main thread, it is also interfering with the mainloop. To prevent this you can use a secondary Thread to run your function. It's recommended that secondary threads are not given access to tkinter objects. Allen B.Taylor, author of mtTkinter, states:
The problems stem from the fact that the _tkinter module attempts to
gain control of the main thread via a polling technique when
processing calls from other threads.
If it succeeds, all is well. If it fails (i.e., after a timeout), the
application receives an exception with the message: "RuntimeError:
main thread is not in main loop".
You can have the secondary thread put information into a Queue. Then have a function that checks the Queue every x milliseconds, within the mainloop, via the after() method.
First, decide what you want the value of the Progressbar's maximum option to be.
This is the Progressbar's maximum indicator value (how many units are required to fill the Progressbar).
For example, you could set maximum=4 and then put the appropriate indicator value into the Queue after each of your four functions. The main thread can then retrieve these values (from the Queue) to set the progress via a tkinter.IntVar().
(Note that if you use progbar.step(), the Progressbar resets to 0 (empty) at the end, instead of reaching 4 (completely filled).)
Here's a quick look at how you can use a tkinter.IntVar() with a Progressbar:
int_var = tkinter.IntVar()
pb_instance = ttk.Progressbar(root, maximum=4)
pb_instance['variable'] = int_var
pb_instance.pack()
# completely fill the Progressbar
int_var.set(4)
# get the progress value
x = int_var.get()
Here's an example based on your own (renamed the "main" function "arbitrary"):
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
import Queue as queue
else:
from tkinter import ttk
import queue
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.int_var = tkinter.IntVar()
progbar = ttk.Progressbar(self.root, maximum=4)
# associate self.int_var with the progress value
progbar['variable'] = self.int_var
progbar.pack()
self.label = ttk.Label(self.root, text='0/4')
self.label.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.int_var.set(0) # empty the Progressbar
self.label['text'] = '0/4'
# create then start a secondary thread to run arbitrary()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
# check the Queue in 50ms
self.root.after(50, self.check_que)
def check_que(self):
while True:
try: x = que.get_nowait()
except queue.Empty:
self.root.after(25, self.check_que)
break
else: # continue from the try suite
self.label['text'] = '{}/4'.format(x)
self.int_var.set(x)
if x == 4:
self.b_start['state'] = 'normal'
break
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
que.put(1)
func_b()
que.put(2)
func_c()
que.put(3)
func_d()
que.put(4)
que = queue.Queue()
gui = GUI_Core() # see GUI_Core's __init__ method
gui.root.mainloop()
If all you want is something that indicates to the user that there is activity
you can set the Progressbar's mode option to 'indeterminate'.
The indicator bounces back and forth in this mode (the speed relates to the maximum option).
Then you can call the Progressbar's start() method directly before starting the secondary thread;
and then call stop() after secondary_thread.is_alive() returns False.
Here's an example:
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
func_b()
func_c()
func_d()
gui = GUI_Core()
gui.root.mainloop()
→ Progressbar reference
You must be using:
self.pgBar.step(x)
where 'x' is the amount to be increased in progressbar.
for this to get updated in your UI you have to put
self.window.update_idletasks() after every self.pgBar.step(x) statement

Python GUI buttons wont execute

I'm writing a basic war-driving program. I have gotten it to loop the command to pull all the wireless access points near by. The problem is my stop button doesn't work and I am unable to update the label(I'm not even sure if I can update the label).
import sys, os, subprocess, re
from Tkinter import *
missionGO = 0
count = 0
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.start = Button(frame, text="Start", fg="green",
command=self.startButtonClick)
self.start.grid(row=3)
self.stop = Button(frame, text="Stop", fg="red",
command=self.stopButtonClick)
self.stop.grid(row=3, column=1)
self.totalSSIDLabel = Label(frame, text="Current Access Points: ")
self.totalSSIDLabel.grid(row=0)
self.totalSSID = Label(frame, text=count)
self.totalSSID.grid(row=0, column=1)
def startButtonClick(self):
missionGO = 1
while (missionGO == 1):
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
return
def stopButtonClick(self):
missionGO = 0
return
def stop(event):
missionGO = 0
# Finds all wireless AP
def getAccessPoints():
X = subprocess.check_output("netsh wlan show network mode=Bssid",
shell=True)
return X
def numberOfAccessPoints(file):
count = 0
words = file.split()
for line in words:
if re.match('SSID', line):
count = count + 1
return count
#Main
root = Tk()
app = App(root)
root.mainloop()
Tkinter is single threaded. That means that while you are in the while loop inside startButtonClick, no other events are processed. The stop button won't call its command until the startButtonClick function finishes
You need to remember that your program is already running a global infinite loop: the event loop. There's no reason to put another infinite loop inside it. When you want something to run forever, the trick is to put one iteration on the event loop, then when it runs it puts another iteration on the event loop.
The other key to this is to make sure that one iteration of the loop is fast -- it needs to be well under a second (more like under 100ms) or the UI will become laggy.
The logic looks something like this:
def startButtonClick(self):
self.missionGO = 1
self._do_one_iteration()
def _do_one_iteration(self):
if self.missionGO == 1:
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
# this adds another iteration to the event loop
self.after(10, self._do_one_iteration)
def stopButtonClick(self):
self.missionGO = 0
I think the main thread is hanging in the while loop of the start button click. Since it's busy it won't even notice the stop button has been pressed.
I can't tell you exactly why your stop button doesn't work, but I think I got the idea of your programm. My suggestion is to establish two threads. The first thread for the UI, and the second for constantly checking wireless networks with given interval (your current code checks ASAP - bad practice, you should pause within the loop.
Since I have not dealt with multithreading in Tkinter, I can only provide you with entry points:
threading Module
time.sleep for updating the nearby networks every second or similar
Is there a way to request a function to be called on Tkinter mainloop from a thread which is not the mainloop?
Tkinter: invoke event in main loop
Good luck!

Categories

Resources