I'm trying to read a very slow sensor (1-wire) while still operating other functions. I've replaced the read sensors with a sleep of 3 sec. I'd like to understand if I can have below time concurrently print accurate time every second while the sensor (wait 3sec.) occurs. This threading concept is new to me.
import time
import threading
from tkinter import Tk
def events_every_second(): #update clock every second
right_now = time.strftime("%I:%M:%S %p")#("%H:%M:%S")
print("time is now",right_now)
root.after(1000, events_every_second)
def one_wire():
time.sleep(3)
print("one_wire loop")
root.after(3010, one_wire)
root = Tk()
t_one_wire = one_wire()
thread_one_wire = threading.Thread(target = t_one_wire)
thread_one_wire.start()
t_ees = events_every_second
thread_ees = threading.Thread(target = t_ees)
thread_ees.start()
root.mainloop()
Just the function that does the lenghty sensor read needs to be
in a separate thread.
You can use a queue.Queue to obtain data from it -
in the example bellow I inserted the value fetching in the
every_one_sec function.
The every_one_sec function is handled by Tkinter scheduling events -
no need to creat a thread for it.
Other than that, the most incorrect part in your code was doing a
full call to one_wire before creating the thread in
t_one_wire = one_wire() - and the fact that it would run also require
tkinter to call it back. Tkinter wants to run all its events in the same
thread - so this would lead to problems.
I hope the comments bellow are enough for a better comprehension
import time
import threading
from queue import Queue, Empty
from tkinter import Tk
def some_ui_code():
global stop_reading
stop_reading = True
def events_every_second(): #update clock every second
right_now = time.strftime("%I:%M:%S %p")#("%H:%M:%S")
print("time is now",right_now)
try:
result = queue.get(block=False)
except Empty:
result = None
if result is not None:
# code to display the result read from the sensor in tkinter interface goes here
...
# tkinter, not a separate thread, handles this
root.after(1000, events_every_second)
def one_wire(queue):
# this is handled in a separate thread, and "does not know" about tkinter at all
while not stop_reading:
result = call_to_read_sensor() # takes 3 seconds
queue.put(result)
print("one_wire loop")
time.sleep(0.1) # actual call to time.sleep to space sensor readings, if needed.
root = Tk()
stop_reading = False
queue = Queue()
# start events_every_second - tkinter will handle the subsequent calls with the `root.after` scheduling
events_every_second()
thread_one_wire = threading.Thread(target=t_one_wire, args=(queue,))
thread_one_wire.start()
root.mainloop()
You should not use root.after(3010,one_wire) this will cause your Tk GUI to hang for 3 seconds and don't update.
You want to create a new Thread for this function so it does not stop your Tk app.
def one_wire():
time.sleep(3)
print("one_wire_loop")
create a new thread for this function.
import threading
one_wire_thread = threading.Thread(target = one_wire, args = ())
one_wire_thread.start()
Note that the code above will only run your function once. you could create a wrapper function with a while True: in there to keep it running, in the wrapper function you could also check for condition to stop and break the function and thus stop the one_wire function. you could also do this in the one_wire function:
def one_wire():
while True:
time.sleep(3)
print("one_wire_loop")
if {"condition to stop reading sensor"}:
break
Related
I am working on a project, where I have to read values from serial port and display them on tkinter GUI. I am using continous threading module of python. I am using a continous thread to read the data on serial port continously after every 0.5s, but now i want to stop this continous thread. So how should I stop it ?
This is the function which I am calling when a checkbutton is presssed
def scan():
print("in scan")
btn1_state = var1.get()
print("Scan: %d"%btn1_state)
t1 = continuous_threading.PeriodicThread(0.5, readserial)
if(btn1_state == 1):
t1.start()
else:
print("entered else ")
t1.stop() #I am using stop() but the thread doesn't stop
Please Help
The problem is likely that you are using a blocking read function in your readserial function. It needs a timeout. I can reproduce with this code:
import time
import continuous_threading
time_list = []
def save_time():
while True:
time.sleep(1)
time_list.append(time.time())
th = continuous_threading.PeriodicThread(0.5, save_time)
th.start()
time.sleep(4)
th.join()
print(time_list)
This never exits.
Modified from the examples.
Since continuous_threading expects it's event loop to be in control, it never gets to the stop event.
I am learning python on my own and my level is probably a poor excuse for a "script kiddie" as I kinda understand and mostly end up borrowing and mashing together different scripts till it does what I want. However this is the first time I'm trying to create a GUI for one of the scripts I have. I'm using PySimpleGUI and I've been able to understand it surprisingly well. All but one thing is working the way I want it to.
The issue is I want to stop a running daemon thread without exiting the GUI. If I get the stop button to work the GUI closes and if the GUI does not close it doesn't stop the thread. The issue is between lines '64-68'. I have tried a few things and just put a place holder on line '65' to remember that I was trying to keep the GUI ("Main Thread" in my head-speak) running. The script will run in this state but the 'Stop' button does not work.
Note: I put a lot of comments in my scripts so I remember what each part is, what it does and what I need to clean up. I don't know if this is a good practice if I plan on sharing a script. Also, if it matters, I use Visual Studio Code.
#!/usr/local/bin/python3
import PySimpleGUI as sg
import pyautogui
import queue
import threading
import time
import sys
from datetime import datetime
from idlelib import window
pyautogui.FAILSAFE = False
numMin = None
# ------------------ Thread ---------------------
def move_cursor(gui_queue):
if ((len(sys.argv)<2) or sys.argv[1].isalpha() or int(sys.argv[1])<1):
numMin = 3
else:
numMin = int(sys.argv[1])
while(True):
x=0
while(x<numMin):
time.sleep(5) # Set short for debugging (will set to '60' later)
x+=1
for i in range(0,50):
pyautogui.moveTo(0,i*4)
pyautogui.moveTo(1,1)
for i in range(0,3):
pyautogui.press("shift")
print("Movement made at {}".format(datetime.now().time()))
# --------------------- GUI ---------------------
def the_gui():
sg.theme('LightGrey1') # Add a touch of color
gui_queue = queue.Queue() # Used to communicate between GUI and thread
layout = [ [sg.Text('Execution Log')],
[sg.Output(size=(30, 6))],
[sg.Button('Start'), sg.Button('Stop'), sg.Button('Click Me'), sg.Button('Close')] ]
window = sg.Window('Stay Available', layout)
# -------------- EVENT LOOP ---------------------
# Event Loop to process "events"
while True:
event, values = window.read(timeout=100)
if event in (None,'Close'):
break
elif event.startswith('Start'): # Start button event
try:
print('Starting "Stay Available" app')
threading.Thread(target=move_cursor,
args=(gui_queue,), daemon=True).start()
except queue.Empty:
print('App did not run')
elif event.startswith('Stop'): # Stop button event
try:
print('Stopping "Stay Available" app')
threading.main_thread # To remind me I want to go back to the original state
except queue.Empty:
print('App did not stop')
elif event == 'Click Me': # To see if GUI is responding (will be removed later)
print('Your GUI is alive and well')
window.close(); del window
if __name__ == '__main__':
gui_queue = queue.Queue() # Not sure if it goes here or where it is above
the_gui()
print('Exiting Program')
From this answer: create the class stoppable_thread.
Then: store the threads on a global variable:
# [...]
# store the threads on a global variable or somewhere
all_threads = []
# Create the function that will send events to the ui loop
def start_reading(window, sudo_password = ""):
While True:
window.write_event_value('-THREAD-', 'event')
time.sleep(.5)
# Create start and stop threads function
def start_thread(window):
t1 = Stoppable_Thread(target=start_reading, args=(window,), daemon=True)
t1.start()
all_threads.append(t1)
def stop_all_threads():
for thread in all_threads:
thread.terminate()
Finally, on the main window loop, handle the events that start, stop or get information from the thread.
from pythoncom import PumpWaitingMessages
import pyHook, threading
import tkinter as tk
threadsRun = 1
token = 0
def pas():
while threadsRun:
pass
def listen(startButton):
"""Listens for keystrokes"""
def OnKeyboardEvent(event):
"""A key was pressed"""
global threadsRun
if event.Key == "R":
startButton.config(relief=tk.RAISED, state=tk.NORMAL, text="Start")
threadsRun = 0
return True
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
while threadsRun:
PumpWaitingMessages()
else:
hm.UnhookKeyboard()
def simplegui():
def threadmaker():
"""Starts threads running listen() and pas()"""
startButton.config(relief=tk.SUNKEN, state=tk.DISABLED, text="r=stop listening")
global token, threadsRun
threadsRun = 1
token += 1
t1 = threading.Thread(target=pas, name='pas{}'.format(token))
t2 = threading.Thread(target=listen, args=(startButton,), name='listen{}'.format(token))
t1.start()
t2.start()
def destroy():
"""exit program"""
global threadsRun
threadsRun = 0
root.destroy()
startButton = tk.Button(root, text="Start", command=threadmaker, height=10, width=20)
startButton.grid(row=1, column=0)
quitButton = tk.Button(root, text="Quit", command=destroy, height=10, width=20)
quitButton.grid(row=1, column=1)
root = tk.Tk()
simplegui()
root.mainloop()
Code description:
simplegui() creates two threads to run
pas() and
listen()
simultaneously.
listen() waits for keyboard presses(only r does anything: exits both threads/functions).
pas() does nothing but is needed to reproduce bug.
Problem description:
After clicking start, pressing any button on the keyboard can cause tkinter to stop responding.
~2/3rd of the time r will behave as intended.
I'm using Spyder IDE (python 3.5).
Some observations:
Using print statements, the program will go into while threadsRun loop, in listen(), before the crash, but didn't reach OnKeyboardEvent() print statement.
Can wait a long time before pressing a key and it may freeze.
Can press a key instantly after pressing start and it may function as intended.
Removing t1 = ... and t1.start() lines allows program to run bug free.
Alternatively, removing all tkinter code allows program to run bug free.
Mashing a bunch of keys all at once freezes it.
If I place a print statement inside the while threadsRun loop, r will rarely work.
I've read in other posts, tkinter is not thread safe, and to use a queue. But I don't understand how. I also think maybe something else is wrong because it works sometimes. https://www.reddit.com/r/learnpython/comments/1v5v3r/tkinter_uis_toplevel_freezes_on_windows_machine/
Thanks very much for reading.
One attempt I managed to use for threads and queues is the following (replaced used code with multiple pseudo-code entries)
The Class works as a session watchdog and uses sql commands gather logged in users, then uses threads for their location detection (geoip)
class SessionWatchdog
import Tkinter as tk
import ttk
import Queue
import Locator
class SessionWatchdog(ttk.Frame):
"""
Class to monitor active Sessions including Location in a threaded environment
"""
__queue = None
__sql = None
def __init__(self, *args, **kwargs):
#...
# Create the Queue
self.__queue = Queue.Queue()
def inqueue(self):
""" Handle Input from watchdog worker Thread """
if self.__queue.empty():
return
while self.__queue.qsize():
"""
Use
try:
self.__queue.get()
finally:
self.__queue.task_done()
to retrieve data from the queue
"""
pass
def gather_data(self, queue):
"""
Retrieve online users and locate them
"""
if self.__sql:
threads = []
# gather data via sql
# ....
# ....
for data in sql_result:
thread = Locator(queue, data)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
Locator to fill the queue:
class Locator
import threading
import Queue
class Locator(threading.Thread):
"""
docstring
"""
__base_url = "http://freegeoip.net/json/{}"
def __init__(self, queue, user_information):
threading.Thread.__init__(self)
self.queue = queue
self.user_information = user_information
def run(self):
""" add location information to data (self.user_information)
to improve performance, we put the localization in single threads.
"""
located_user = []
# locate the user in a function, NOT method!
self.queue.put(located_user, False)
Here's the relevant part of the code:
x=None
def pp():
global x
x=MyClass()
x.start()
def main():
global x
p=Process(target=pp)
p.start()
while x==None:
print("Not yet...")
while 1:
print(x.getoutput(),end="")
p.join()
if __name__=='__main__':
main()
The x.start() method opens a TKInter window, so it runs forever (or at least until the user closes the window). I'm trying to run another process that would get information from the used window, but it doesn't work.
How can i make it work?
I feel like the first thing to point out here is that each child process will import the main script and have it's own local copy. It's not possible to use global variables in the way you're trying to here, because the child processes don't use the same namespace. If you're set on using multiprocessing for this, you need to use a communication pipe of some description, as described in the following documentation:
http://pymotw.com/2/multiprocessing/communication.html#multiprocessing-queues
I am a little curious what your ultimate objective is here for using multiprocessing. Still, if you really want to do it, it's possible:
import multiprocessing
import tkinter
import time
def worker(q):
#q is a queue for communcation.
#Set up tkinter:
root = tkinter.Tk()
localvar = tkinter.StringVar()
wind = tkinter.Entry(root,textvariable=localvar)
wind.grid()
#This callback puts the contents of the entry into the queue
#when the entry widget is modified
def clbk(name,index,mode,q=q):
q.put(localvar.get())
localvar.trace_variable('w',clbk)
#Some window dressing that will signal to the main process that
#the window has been closed
def close(root=root,q=q):
q.put('EXIT')
root.destroy()
root.quit()
root.protocol("WM_DELETE_WINDOW",close)
root.mainloop()
def main():
#Make a queue to facilitate communication:
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=worker,args=(queue,))
p.start()
#Wait for input...
while True:
ret = queue.get()
print(ret)
if ret=='EXIT':
break
time.sleep(0.1)
#Finally, join the process back in.
p.join()
if __name__ == '__main__':
main()
Ignoring the window dressing, that will now print text entered into your tkinter entry widget, and exits when the window is closed.
I have an FTP function that traces the progress of running upload but my understanding of threading is limited and i have been unable to implement a working solution... I'd like to add a GUI progress bar to my current Application by using threading. Can someone show me a basic function using asynchronous threads that can be updated from another running thread?
def ftpUploader():
BLOCKSIZE = 57344 # size 56 kB
ftp = ftplib.FTP()
ftp.connect(host)
ftp.login(login, passwd)
ftp.voidcmd("TYPE I")
f = open(zipname, 'rb')
datasock, esize = ftp.ntransfercmd(
'STOR %s' % os.path.basename(zipname))
size = os.stat(zipname)[6]
bytes_so_far = 0
print 'started'
while 1:
buf = f.read(BLOCKSIZE)
if not buf:
break
datasock.sendall(buf)
bytes_so_far += len(buf)
print "\rSent %d of %d bytes %.1f%%\r" % (
bytes_so_far, size, 100 * bytes_so_far / size)
sys.stdout.flush()
datasock.close()
f.close()
ftp.voidresp()
ftp.quit()
print 'Complete...'
Here's a quick overview of threading, just in case :) I won't go into too much detail into the GUI stuff, other than to say that you should check out wxWidgets. Whenever you do something that takes a long time, like:
from time import sleep
for i in range(5):
sleep(10)
You'll notice that to the user, the entire block of code seems to take 50 seconds. In those 5 seconds, your application can't do anything like update the interface, and so it looks like it's frozen. To solve this problem, we use threading.
Usually there are two parts to this problem; the overall set of things you want to process, and the operation that takes a while, that we'd like to chop up. In this case, the overall set is the for loop and the operation we want chopped up is the sleep(10) function.
Here's a quick template for the threading code, based on our previous example. You should be able to work your code into this example.
from threading import Thread
from time import sleep
# Threading.
# The amount of seconds to wait before checking for an unpause condition.
# Sleeping is necessary because if we don't, we'll block the os and make the
# program look like it's frozen.
PAUSE_SLEEP = 5
# The number of iterations we want.
TOTAL_ITERATIONS = 5
class myThread(Thread):
'''
A thread used to do some stuff.
'''
def __init__(self, gui, otherStuff):
'''
Constructor. We pass in a reference to the GUI object we want
to update here, as well as any other variables we want this
thread to be aware of.
'''
# Construct the parent instance.
Thread.__init__(self)
# Store the gui, so that we can update it later.
self.gui = gui
# Store any other variables we want this thread to have access to.
self.myStuff = otherStuff
# Tracks the paused and stopped states of the thread.
self.isPaused = False
self.isStopped = False
def pause(self):
'''
Called to pause the thread.
'''
self.isPaused = True
def unpause(self):
'''
Called to unpause the thread.
'''
self.isPaused = False
def stop(self):
'''
Called to stop the thread.
'''
self.isStopped = True
def run(self):
'''
The main thread code.
'''
# The current iteration.
currentIteration = 0
# Keep going if the job is active.
while self.isStopped == False:
try:
# Check for a pause.
if self.isPaused:
# Sleep to let the os schedule other tasks.
sleep(PAUSE_SLEEP)
# Continue with the loop.
continue
# Check to see if we're still processing the set of
# things we want to do.
if currentIteration < TOTAL_ITERATIONS:
# Do the individual thing we want to do.
sleep(10)
# Update the count.
currentIteration += 1
# Update the gui.
self.gui.update(currentIteration,TOTAL_ITERATIONS)
else:
# Stop the loop.
self.isStopped = True
except Exception as exception:
# If anything bad happens, report the error. It won't
# get written to stderr.
print exception
# Stop the loop.
self.isStopped = True
# Tell the gui we're done.
self.gui.stop()
To call this thread, all you have to do is:
aThread = myThread(myGui,myOtherStuff)
aThread.start()