Not sure I'm running a thread properly - python

I am running a thread to constantly monitor temperature, and update a global variable that other functions call upon to perform their tasks accordingly. Everything works now, and I could live with it. But I wanted to reach out and see if anyone has a smarter way to keep this going until the mainloop ends. Here is how the thread is started...
thread1 = Thread(target = test)
try:
thread1.start()
except (KeyboardInterrupt, SystemExit):
thread1.stop()
sys.exit()
I guess I could make a small script just to let it run. Sorry...
import time
import tkinter
from tkinter import *
from threading import Thread
root = Tk()
timer = True
global counter
counter = 0
var1 = StringVar()
var1.set(counter)
def test():
global counter
while timer:
counter += 1
var1.set(counter)
root.update
time.sleep(1)
testtext = Label(root,textvariable=var1,font='Consolas 24 bold')
testtext.grid(row=1,column=1,sticky="N,S,E,W",padx=10,pady=0)
testtext2 = Label(root,text="SECONDS",font='Consolas 18 bold')
testtext2.grid(row=2,column=1,sticky="N,S,E,W",padx=10,pady=0)
thread1 = Thread(target = test)
try:
thread1.start()
except (KeyboardInterrupt, SystemExit):
thread1.stop()
sys.exit()
root.mainloop()
As you will see, after you close the window, the thread does end, but not very cleanly.
Any ideas?

Tkinter supports protocol handlers.
The most commonly used protocol is called WM_DELETE_WINDOW, and is used to define what happens when the user explicitly closes a window using the window manager.
import time
import tkinter
from tkinter import *
from threading import Thread
def on_closing():
thread1.close()
root.destroy()
def test():
global counter
while timer:
counter += 1
var1.set(counter)
root.update
time.sleep(1)
root = Tk()
timer = True
global counter
counter = 0
var1 = StringVar()
var1.set(counter)
testtext = Label(root,textvariable=var1,font='Consolas 24 bold')
testtext.grid(row=1,column=1,sticky="N,S,E,W",padx=10,pady=0)
testtext2 = Label(root,text="SECONDS",font='Consolas 18 bold')
testtext2.grid(row=2,column=1,sticky="N,S,E,W",padx=10,pady=0)
global thread1 = Thread(target = test)
try:
thread1.start()
except (KeyboardInterrupt, SystemExit):
thread1.stop()
sys.exit()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
Basically, the program listens to the close event and decides what to do as the window is killed in the functuon on_closing().

Got it. Thank you Daniel Reyhanian for pointing me toward the
root.protocol("WM_DELETE_WINDOW", on_closing)
thing. Never seen that before.
Here is the final bit using: os._exit(1)
import os
import time
import tkinter
from tkinter import *
from threading import Thread
root = Tk()
timer = True
global counter
counter = 0
var1 = StringVar()
var1.set(counter)
def on_closing():
os._exit(1)
def test():
global counter
while timer:
counter += 1
var1.set(counter)
root.update
time.sleep(1)
testtext = Label(root,textvariable=var1,font='Consolas 24 bold')
testtext.grid(row=1,column=1,sticky="N,S,E,W",padx=10,pady=0)
testtext2 = Label(root,text="SECONDS",font='Consolas 18 bold')
testtext2.grid(row=2,column=1,sticky="N,S,E,W",padx=10,pady=0)
thread1 = Thread(target = test)
thread1.start()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

Related

Sharing variables between threads in Python tkinter

Is it possible to get the GUI to update variable 'a' to update in real time while 'thread2' increments the value?
import tkinter as tk
from threading import Thread
import time
a = 0 # global variable
def thread1(threadname):
root = tk.Tk()
w = tk.Label(root, text=a)
w.pack()
root.mainloop()
def thread2(threadname):
global a
while True:
a += 1
time.sleep(1)
thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )
thread1.start()
thread2.start()
If I create a loop and print 'a' I get the correct result.
def thread1(threadname):
global a
while True:
print(a)
# root = tk.Tk()
# w = tk.Label(root, text=a)
# w.pack()
# root.mainloop()
Any help would be appreciated
When you create your label, that's not a "live" connection. That passes the current value of the variable a. You then enter your main loop, and that thread does nothing else until the application exits. You need to send the new value to a function that executes as part of the main thread, and that function will need access to the label.
This works:
import tkinter as tk
from threading import Thread
import time
class GUI(object):
def __init__(self):
self.a = 0
self.w = tk.Label(root, text=self.a)
self.w.pack()
thread2 = Thread( target=self.thread2, args=("Thread-2", ) )
thread2.start()
def thread2(self,threadname):
while True:
self.a += 1
root.after_idle(self.update)
time.sleep(1)
def update(self):
self.w.config(text=self.a)
root = tk.Tk()
gui = GUI()
root.mainloop()
It is possible to make a live connection using textvariable, but then you have to change the type of a to a tkinter.StringVariable. Check here: Update Tkinter Label from variable
Try using a <tkinter.Tk>.after loop like this:
import tkinter as tk
def loop():
global a
a += 1
label.config(text=a)
# run `loop` again in 1000 ms
root.after(1000, loop)
a = 0
root = tk.Tk()
label = tk.Label(root, text=a)
label.pack()
loop() # Start the loop
root.mainloop()
I used a .after script because tkinter and threading don't go together very well. Sometimes tkinter can crash without even giving you an error message if you try to call some tkinter commands from the second thread.

TkInter: how to wait until callback from after method has completed

When using the Tkinter .after method, the code continues passed without waiting for the callback to complete.
import tkinter as tk
import tkinter.ttk as ttk
import time
from datetime import datetime
global i
i = 0
global j
j = 0
def SomeFunction():
global i
for num in range(10):
i+=1
x = barVar.get()
barVar.set(x+5)
histrun_mainWindow.update()
time.sleep(2)
def SecondFunction():
global j
for num in range(10):
j+=1
x = barVar.get()
barVar.set(x+5)
histrun_mainWindow.update()
time.sleep(2)
def Load(run_date):
histrun_mainWindow.after(50, SomeFunction)
histrun_mainWindow.after(50, SecondFunction)
global i, j
print 'Number is :', i + j
histrun_mainWindow = tk.Tk()
run_date = datetime.today().date()
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(histrun_mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(histrun_mainWindow, text='Run for this date ' + str(run_date), command=lambda:Load(run_date))
button.grid(row=0, column=0)
histrun_mainWindow.mainloop()
This example shows what is happening. The .after() calls the Load() function but doesn't wait for Load() to complete, it goes straight on to the next line.
I want i to print as 10 but because the .after() doesn't wait for Load() to finish it's additions, i prints as 0
The progress bar continues to update so I know that Load was called as it continues in the background after i is printed
Question: the progress bar doesn't update - the window freezes until all functions have completed
Use a Thread to prevent main loop from freezing.
Your functions - SomeFunction, SecondFunction - may also in global namespace.
Then you have to pass self.pbar as paramter, e.g. SomeFunction(pbar): ... f(self.pbar).
Note:
You see a RuntimeError: main thread is not in main loop
if you .destroy() the App() window while the Thread is running!
import tkinter as tk
import threading
class App(tk.Tk):
def __init__(self):
super().__init__()
btn = tk.Button(self, text='Run',
command=lambda :threading.Thread(target=self.Load).start())
btn.grid(row=0, column=0)
self.pbar = ttk.Progressbar(self, maximum=2 *(5 * 5), mode='determinate')
self.pbar.grid(row=1, column=0)
def SomeFunction(self):
for num in range(5):
print('SomeFunction({})'.format(num))
self.pbar['value'] += 5
time.sleep(1)
return num
def SecondFunction(self):
for num in range(5):
print('SecondFunction({})'.format(num))
self.pbar['value'] += 5
time.sleep(1)
return num
def Load(self):
number = 0
for f in [self.SomeFunction, self.SecondFunction]:
number += f()
print('Number is :{}'.format(number))
if __name__ == "__main__":
App().mainloop()
Output:
SomeFunction(0)
SomeFunction(1)
SomeFunction(2)
SomeFunction(3)
SomeFunction(4)
SecondFunction(0)
SecondFunction(1)
SecondFunction(2)
SecondFunction(3)
SecondFunction(4)
Number is :8
Tested with Python: 3.5
*)Could not test with Python 2.7

Tkinter UI Becomes Unresponsive While For Loop Is Running

I am having a problem where my Tkinter UI becomes completley stuck and non-interactive while a for loop is running. My example code print "Looping" while it is in the loop and there is a "Cancel" button on the UI which is supposed to stop the loop, but since I am unable to click the "Cancel" button the loop can not be stopped. So my question is how can I make my tkinter UI usable while a loop is running. Here is the example code:
from tkinter import*
import time
root = Tk()
i=10
flag = False
def loop():
flag = True
for i in range(100):
if flag == True:
time.sleep(0.5)
print("Looping")
def canc():
flag = False
btn = Button(root, text="Start Loop", command=loop).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()
I have tried creating a new thread for the loop function but this does not work.
Updated code, the UI is responsive, but nothing happens when cancel is pressed:
from tkinter import*
import threading
import time
root = Tk()
i=10
flag = False
def loop():
flag = True
for i in range(10):
if flag == True:
time.sleep(0.5)
print("Looping")
def run():
threading.Thread(target=loop).start()
def canc():
flag = False
btn = Button(root, text="Start Loop", command=run).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()
'flag' isnt a global variable so when it is set to False in canc(), the value of the 'flag' local variable in loop() isnt changed and the loop therefore isnt stopped
also root.update() needs to be used to update the GUI
Remedial actions:
from tkinter import*
import threading
import time
root = Tk()
def loop():
global flag
flag = True
for i in range(10):
if flag == True:
root.update()
time.sleep(0.5)
print("Looping")
def canc():
global flag
flag = False
btn = Button(root, text="Start Loop", command=loop).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()
I found a way around that problem:
I started my time-consuming job inside a thread and I checked if my thread is still running in a while loop, and inside that, I did update my Tkinter root.
here is my code:
def start_axis(input):
print(input)
time.sleep(5)
def axis():
t = threading.Thread(target=start_axis, args=("x"))
t.start()
while t.is_alive():
try:
root.update()
except:
pass
the args part is important so the thread doesn't call the function immediately

How to kill time.sleep() in python 3 while compiler is sleeping

I want to come out of the loop immediately after pressing the stop button. But with this code, i could able to come out only after executing current iteration and next iteration.
It is very important for my application since iam going to use this for automating instruments, where operations have to be stopped immediately after pressing stop button.
# -*- coding: utf-8 -*-
"""
Created on Sun Jun 17 17:01:12 2018
#author: Lachu
"""
import time
from tkinter import *
from tkinter import ttk
root=Tk()
def start():
global stop_button_state
for i in range(1,20):
if (stop_button_state==True):
break
else:
print('Iteration started')
print('Iteration number: ', i)
root.update()
time.sleep(10)
print('Iteration completed \n')
def stop_fun():
global stop_button_state
stop_button_state=True
start=ttk.Button(root, text="Start", command=start).grid(row=0,column=0,padx=10,pady=10)
p=ttk.Button(root, text="Stop", command=stop_fun).grid(row=1,column=0)
stop_button_state=False
root.mainloop()
It's generally not a good idea to use time.sleep with GUI programs because it puts everything to sleep, so the GUI can't update itself, or respond to events. Also, it gets messy when you want to interrupt sleep.
I've adapted your code to use a Timer from the threading module. We can easily interrupt this Timer instantly, and it doesn't block the GUI.
To make this work, I moved your counting for loop into a generator.
If you press the Start button while a count is in progress it will tell you that it's already counting. When a count cycle is finished, either by pressing Stop, or by getting to the end of the numbers, you can press Start again to start a new count.
import tkinter as tk
from tkinter import ttk
from threading import Timer
root = tk.Tk()
delay = 2.0
my_timer = None
# Count up to `hi`, one number at a time
def counter_gen(hi):
for i in range(1, hi):
print('Iteration started')
print('Iteration number: ', i)
yield
print('Iteration completed\n')
# Sleep loop using a threading Timer
# The next `counter` step is performed, then we sleep for `delay`
# When we wake up, we call `sleeper` to repeat the cycle
def sleeper(counter):
global my_timer
try:
next(counter)
except StopIteration:
print('Finished\n')
my_timer = None
return
my_timer = Timer(delay, sleeper, (counter,))
my_timer.start()
def start_fun():
if my_timer is None:
counter = counter_gen(10)
sleeper(counter)
else:
print('Already counting')
def stop_fun():
global my_timer
if my_timer is not None:
my_timer.cancel()
print('Stopped\n')
my_timer = None
ttk.Button(root, text="Start", command=start_fun).grid(row=0, column=0, padx=10, pady=10)
ttk.Button(root, text="Stop", command=stop_fun).grid(row=1,column=0)
root.mainloop()
You are probably better off using root.after than threads:
In any events, as other pointed out, using time.sleep is a bad idea in a GUI.
You should also not name your buttons the same as your functions.
calling root.update, is also not necessary here.
from tkinter import *
from tkinter import ttk
def start_process(n=0, times=10):
n += 1
if not stop_button_state and n < times:
print('Iteration started')
print(f'Iteration number: {n}')
print('Iteration completed \n')
root.after(1000, start_process, n)
else:
print('stopping everything')
def stop_fun():
global stop_button_state
stop_button_state = True
if __name__ == '__main__':
root = Tk()
start = ttk.Button(root, text="Start", command=start_process)
start.grid(row=0, column=0, padx=10, pady=10)
p = ttk.Button(root, text="Stop", command=stop_fun)
p.grid(row=1, column=0)
stop_button_state = False
root.mainloop()
Without using a separate thread, you could always iterate over the sleep command, which would make the code more responsive.
e.g. This would reduce your wait between clicking stop and loop exit to 1/10th of a second, whilst retaining a 10 second gap between loops.
# -*- coding: utf-8 -*-
"""
Created on Sun Jun 17 17:01:12 2018
#author: Lachu
"""
import time
from tkinter import *
from tkinter import ttk
root=Tk()
stop_button_state=False
def start():
global stop_button_state
for i in range(1,20):
if (stop_button_state==True):
break
print('Iteration started')
print('Iteration number: ', i)
for i in range(100):
root.update()
time.sleep(0.1)
if (stop_button_state==True):
break
print('Iteration completed \n')
def stop_fun():
global stop_button_state
stop_button_state=True
ttk.Button(root, text="Start", command=start).grid(row=0,column=0,padx=10,pady=10)
ttk.Button(root, text="Stop", command=stop_fun).grid(row=1,column=0)
root.mainloop()

How can I have a ttk.progressbar call a function when it is completed (Python)?

I am trying to have a ttk progressbar call a function that adds to a value that is displayed in the Tkinter window. I currently have a function that is called at the same time the progressbar begins:
def bar1_addVal():
global userMoney
userMoney += progBar1_values.value
moneyLabel["text"] = ('$' + str(userMoney))
canvas1.after((progBar1_values.duration*100), bar1_addVal)
return
but I cannot seem to get the exact amount of time it takes for the progressbar to finish each iteration. Is there a way to have the progressbar call a function every time it completes?
You can use threading to check for the variable in a loop. Then you wont interrupt the main loop.
I made a little example of this:
import threading, time
from ttk import Progressbar, Frame
from Tkinter import IntVar, Tk
root = Tk()
class Progress:
val = IntVar()
ft = Frame()
ft.pack(expand=True)
kill_threads = False # variable to see if threads should be killed
def __init__(self):
self.pb = Progressbar(self.ft, orient="horizontal", mode="determinate", variable=self.val)
self.pb.pack(expand=True)
self.pb.start(50)
threading.Thread(target=self.check_progress).start()
def check_progress(self):
while True:
if self.kill_threads: # if window is closed
return # return out of thread
val = self.val.get()
print(val)
if val > 97:
self.finish()
return
time.sleep(0.1)
def finish(self):
self.ft.pack_forget()
print("Finish!")
progressbar = Progress()
def on_closing(): # function run when closing
progressbar.kill_threads = True # set the kill_thread attribute to tru
time.sleep(0.1) # wait to make sure that the loop reached the if statement
root.destroy() # then destroy the window
root.protocol("WM_DELETE_WINDOW", on_closing) # bind a function to close button
root.mainloop()
Edit: Updated the answer, to end the thread before closing window.

Categories