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.
Related
I am using multiprocessing module along with PySimpleGUI.
Instead of Multiline everything gets printed in my IDE console. This problem only happens when I use multiprocessing. Other function with sg.cprint statement and even with simple print statement would print the result in Multiline, as it should.
Here's my code, that shows the problem
import PySimpleGUI as sg
import multiprocessing
def func(link: str):
sg.cprint(link)
sg.cprint('string')
print(link)
print('string')
def make_window():
layout = [[sg.Multiline(key='-Multiline-', reroute_stdout=True)],
[[sg.Button('Start', key='-Start-')]]]
window = sg.Window('Forum', layout, finalize=True)
sg.cprint_set_output_destination(window, '-Multiline-')
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == '-Start-':
sg.cprint('Process has started')
process = multiprocessing.Process(target=func,
args=('https://stackoverflow.com/questions/ask',),
daemon=True)
process.start()
window.close()
if __name__ == '__main__':
make_window()
I have tried to reroute everything to Multiline with reroute_stdout=True — doesn't work.
According to so called "Cookbook" of PySimpleGUI, it's possible to reroute print like this:
window['-Multiline-'].print('Testing 1 2 3')
It doesn't work if I put something like that in my function (I assume that is because my function is above the GUI code)
In conclusion - the issue doesn't appear when I use threading module. But multiprocessing solves other problem - it allows me to terminate a process with .terminate(). Couldn't do that with threadingas easily.
My application uses OOP and a bit more complicated than the code I provided.
When running a subprocess, you get an entirely new environment. You can't share global variables for example.
Python essentially runs a completely new Python interpreter instance whenever you start() a multiprocess.Process instance and since different processes get different stacks they don't get to share the internal memory so Python actually pickles the passed arguments, sends them to the Process and then unpickles them there creating an illusion that both processes (the parent and the child) have the same data.
I know nothing about library multiprocessing, following code work, but maybe not perfect.
from time import sleep
import threading
import multiprocessing
import PySimpleGUI as sg
def func(queue:multiprocessing.Queue, link: str):
for i in range(10):
queue.put(f'Message #{i}')
sleep(1)
def thread(window, url):
queue = multiprocessing.Queue()
process = multiprocessing.Process(target=func, args=(queue, url), daemon=True)
process.start()
while process.is_alive():
if not queue.empty():
line = queue.get()
window.write_event_value("Message", line)
while not queue.empty():
line = queue.get()
window.write_event_value("Message", line)
window.write_event_value("Message", 'Process has stopped')
def make_window():
layout = [[sg.Multiline(key='-Multiline-', size=(40, 10), reroute_stdout=True)],
[[sg.Button('Start', key='-Start-')]]]
window = sg.Window('Forum', layout, finalize=True)
sg.cprint_set_output_destination(window, '-Multiline-')
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == '-Start-':
sg.cprint('Process has started')
url = 'https://stackoverflow.com/questions/ask'
threading.Thread(target=thread, args=(window, url), daemon=True).start()
elif event == 'Message':
message = values[event]
print(message)
window.close()
if __name__ == '__main__':
make_window()
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'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
I'm writing a simple time tracking application in Python3 and PyQt5. Time is tracked in separate thread. Function that this thread is running doesn't access GUI code. On Windows10 application freezes after trying to close it. It's caused by calling thread.join(). I need to end the process in task manager to close it. On Linux Mint it works fine. I'm using threads from threading library. It doesn't work also with QThread's. If I comment out the thread.join() line it closes without a problem, but the code that's running by this thread doesn't finish.
Thread is initialized in __init__() function of Window class.
self.trackingThread = Thread(target = self.track)
Function that is responsible for tracking time:
def track(self):
startTime = time()
lastWindowChangeTime = startTime
while self.running:
# check if active window has changed
if self.active_window_name != get_active_window_name():
if self.active_window_name in self.applications_time:
self.applications_time[self.active_window_name] += int(time() - lastWindowChangeTime) // 60 # time in minutes)
else:
self.applications_time[self.active_window_name] = int(time() - lastWindowChangeTime) // 60 # time in minutes
lastWindowChangeTime = time()
self.active_window_name = get_active_window_name()
totalTime = int(time() - startTime) // 60 # time in minutes
if date.today() in self.daily_time:
self.daily_time[date.today()] += totalTime
else:
self.daily_time[date.today()] = totalTime
Joining the thread:
def saveAndQuit(self):
self.running = False
self.trackingThread.join() # the line that's causing application freeze
self.save()
QApplication.instance().quit()
EDIT:
Example:
https://pastebin.com/vt3BfKJL
relevant code:
def get_active_window_name():
active_window_name = ''
if system() == 'Linux':
active_window_name = check_output(['xdotool', 'getactivewindow', 'getwindowname']).decode('utf-8')
elif system() == 'Windows':
window = GetForegroundWindow()
active_window_name = GetWindowText(window)
return active_window_name
EDIT2:
After removing those 2 lines app closes without any problem. Is there any other way of getting active window name on Windows except win32gui?:
window = GetForegroundWindow()
active_window_name = GetWindowText(window)
The issue occurs because GetWindowText() is blocking, and so your thread can never join. To understand why, we have to delve into the win32 documentation
If the target window is owned by the current process, GetWindowText causes a WM_GETTEXT message to be sent to the specified window or control. If the target window is owned by another process and has a caption, GetWindowText retrieves the window caption text. If the window does not have a caption, the return value is a null string. This behavior is by design. It allows applications to call GetWindowText without becoming unresponsive if the process that owns the target window is not responding. However, if the target window is not responding and it belongs to the calling application, GetWindowText will cause the calling application to become unresponsive.
You are attempting to join the thread from within a function (saveAndQuit) that has been called by the Qt event loop. As such, until this function returns, the Qt event loop will not process any messages. This means the call to GetWindowText in the other thread has sent a message to the Qt event loop which won't be processed until saveAndQuit finishes. However, saveAndQuit is waiting for the thread to finish, and so you have a deadlock!
There are several ways to solve the deadlock, probably the easiest to implement is to recursively call join, with a timeout, from the Qt event loop. It's somewhat "hacky", but other alternatives mean things like changing the way your thread behaves or using QThreads.
As such, I would modify your saveAndQuit as follows:
def saveAndQuit(self):
self.running = False
self.trackingThread.join(timeout=0.05)
# if thread is still alive, return control to the Qt event loop
# and rerun this function in 50 milliseconds
if self.trackingThread.is_alive():
QTimer.singleShot(50, self.saveAndQuit)
return
# if the thread has ended, then save and quit!
else:
self.save()
QApplication.instance().quit()
I had a similar problem and someone here on SO advised me to use something like this:
class MyThread(QThread):
def __init__(self):
super().__init__()
# initialize your thread, use arguments in the constructor if needed
def __del__(self):
self.wait()
def run(self):
pass # Do whatever you need here
def run_qt_app():
my_thread = MyThread()
my_thread.start()
qt_app = QApplication(sys.argv)
qt_app.aboutToQuit.connect(my_thread.terminate)
# Setup your window here
return qt_app.exec_()
Works fine for me, my_thread runs as long as qt_app is up, and finishes it's work on quit.
edit: typos
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.