I want a sound to play when I click a button in PyQT5.
Playing a sound appears to be a blocking operation, so GUI is unresponsive. Thus I want to start a new thread, play sound, and delete the thread, all in a non-blocking manner.
I create a thread class
class playSoundThread(QtCore.QThread):
def __init__(self, soundpath):
QtCore.QThread.__init__(self)
self.soundpath = soundpath
def __del__(self):
self.wait()
print("Thread exited")
def run(self):
playsound(self.soundpath)
And run it as follows
class MainClass(...):
...
def playsound(self, soundKey):
self.thisSoundThread = playSoundThread(self.sounds[soundKey])
self.thisSoundThread.start()
Everything works fine and is non-blocking. The only problem is that the thread does not get deleted when the sound stops playing. I have tried calling del self.thisSoundThread, but this operation seems to be blocking, defeating the point.
What is the correct way to exit a thread after completion in a non-blocking way?
Why it should get deleted? I do not see any call of "del" and you assign it into instance so GC also doesnt because there is still existing reference.
If you want to delete it you have to do something like this:
class MainClass(...):
...
def playsound(self, soundKey):
self.thisSoundThread = playSoundThread(self.sounds[soundKey])
self.thisSoundThread.finished.connect(self.threadfinished)
self.thisSoundThread.start()
def threadfinished(self)
del self.thisSoundThread
# or set it to None
Related
I intend to have a GUI where one (later three) threads read live data from different sources with an adjustable interval (e.g. 10s) and plot these data in the main window.
I am using PyQt5 and python 3.6.
The reading is performed in an infinite loop in a worker thread as such:
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval):
QtCore.QObject.__init__(self)
self.directory=directory
self.stillrunning = True
self.refreshtime = interval
def run(self):
print('Entered run in worker thread')
self.stillrunning = True
while self.stillrunning:
outstring=self.read_last_from_logfile() # data reader function, not displayed
self.output.emit(outstring)
time.sleep(self.refreshtime)
#QtCore.pyqtSlot(int) # never called as loop is blocking?
def check_break(self, val):
if val:
self.stillrunning=False
else:
self.stillrunning = True
The main thread looks like this, start() and stop() are called via pushButtons:
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.worker = ReadingThread(directory, interval)
emit_stop=QtCore.pyqtSignal(int)
def start(self):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)
self.emit_stop.connect(self.worker.check_break)
self.thread.start()
def stop(self):
self.emit_stop.emit(1)
# time.sleep(11)
if self.thread.isRunning(): #did not work either
self.thread.quit()
if self.thread.isRunning(): #did also not work
self.thread.terminate()
return
def print_new_value(self, value): #test function for output of values read by worker thread, working well
print (value)
return
def main():
app = QtWidgets.QApplication(sys.argv)
interval=10 #read every 10s new data
directory="/home/mdt-user/logfiles/T_P_logs"
gui = Window(directory,interval)
gui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
My problem is: How can I let the worker thread in the loop look for incoming signals issued by the main thread? Or differently phrased: How can I have a status variable like my self.stillrunning which can be set/accessed outside the worker thread but checked within the worker thread?
I want to avoid killing the thread with means like self.thread.terminate(), which I did unsuccessfully try.
Help would very much be appreciated. I did a search of course, but the answers given and/or problems stated were either too lengthy for what I assume has to be a simple solution, or not applicable.
I don’t see how above comment could have solved your issue. By design your worker will not be able to receive signals, as the while loop blocks the worker event loop until it breaks and the method finishes. Then all the (throughout the blockage) received signals will be worked. Well, technically your received signals aren’t blocked, they’re just not getting worked, until the event loop is being worked again...
I see two solutions, that work with your design pattern (utilizing move to thread).
Solution 1: QTimer (clean, more QT like solution)
The idea here is to use a QTimer. You provide a time period (in milliseconds) to this timer and every time this period is passed, said timer will perform a task (i.e. call a method/function). As you can even pass 0 ms as time period, you can emulate a while loop like behavior. The upside: The event loop won’t be blocked and after every timeout, received signals will be worked.
I modified your code realizing this solution via QTimer. I think with the code comments this example is somewhat self-explanatory.
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval):
#QtCore.QObject.__init__(self)
super(ReadingThread, self).__init__() # this way is more common to me
self.directory = directory
self.refreshtime = interval
# setting up a timer to substitute the need of a while loop for a
# repetitive task
self.poller = QTimer(self)
# this is the function the timer calls upon on every timeout
self.poller.timeout.connect(self._polling_routine)
def _polling_routine(self):
# this is what's inside of your while loop i.e. your repetitive task
outstring = self.read_last_from_logfile()
self.output.emit(outstring)
def polling_start(self):
# slot to call upon when timer should start the routine.
self.poller.start(self.refreshtime)
# the argument specifies the milliseconds the timer waits in between
# calls of the polling routine. If you want to emulate the polling
# routine in a while loop, you could pass 0 ms...
def polling_stop(self):
# This simply stops the timer. The timer is still "alive" after.
self.poller.stop()
# OR substitute polling_start and polling_stop by toggling like this:
def polling_toggle(self):
poller_active = self.poller.isActive()
if poller_active:
# stop polling
self.poller.stop()
else:
# start polling
self.poller.start(self.refreshtime)
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
emit_start = QtCore.pyqtSignal()
emit_stop = QtCore.pyqtSignal()
def __init__(self, directory, interval):
super(Window, self).__init__()
self.init_worker()
def init_worker(self):
self.thread = QtCore.QThread()
self.worker = ReadingThread(directory, interval)
self.worker.moveToThread(self.thread)
self.worker.output.connect(self.print_new_value)
self.emit_start.connect(self.worker.polling_start)
self.emit_stop.connect(self.worker.polling_stop)
self.thread.start()
def start_polling(self):
self.emit_start.emit()
def stop_polling(self):
self.emit_stop.emit()
def finish_worker(self):
# for sake of completeness: call upon this method if you want the
# thread gone. E.g. before closing your application.
# You could emit a finished sig from your worker, that will run this.
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
def print_new_value(self, value):
print(value)
For a better look at how to do this cleanly with QThread (the complexity here is doing the threading right, the QTimer is relatively trivial): https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
EDIT: Make sure to check out the documentation of the QTimer. You can dynamically set the timeout period and so much more.
Solution 2: Passing a mutable as a control variable
You can e.g. pass a dictionary with a control variable into your worker class/thread and use it to break the loop. This works, as (oversimplified statements follow) threads share common memory and mutable objects in python share the same object in memory (this has been more than thoroughly discussed on SO).
I’ll illustrate this here in your modified code, also illustrated you'll find that the memory id is the same for your control dicts in the main and the worker thread:
class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)
def __init__(self, directory, interval, ctrl):
QtCore.QObject.__init__(self)
self.ctrl = ctrl # dict with your control var
self.directory = directory
self.refreshtime = interval
def run(self):
print('Entered run in worker thread')
print('id of ctrl in worker:', id(self.ctrl))
self.ctrl['break'] = False
while True:
outstring=self.read_last_from_logfile()
self.output.emit(outstring)
# checking our control variable
if self.ctrl['break']:
print('break because flag raised')
# might emit finished signal here for proper cleanup
break # or in this case: return
time.sleep(self.refreshtime)
class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
emit_stop=QtCore.pyqtSignal(int)
def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.ctrl = {'break': False} # dict with your control variable
print('id of ctrl in main:', id(self.ctrl))
# pass the dict with the control variable
self.worker = ReadingThread(directory, interval, self.ctrl)
def start(self):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)
self.thread.start()
def stop(self):
# we simply set the control variable (often refered to as raising a flag)
self.ctrl['break'] = True
This solution almost requires no changes to your code, and I would definitely consider it not QT like or even clean, but it is extremely convenient. And sometimes you don’t want to code your experiment/long running task around the fact that you’re using a GUI toolkit.
This is the only way I know of, that let’s you get around the blocked event loop. If somebody has a cleaner solution to this, please let the world know. Especially as this is the only way, to break out of your long running task in a controlled manner, from multiple points, as you can check for your control variable multiple times throughout your repetitive routine.
For now everything in my application is quite simple because I'm just starting building it. In the end there will be potentially two threads which might run for a long time. Not because they've got a lot of computing to do, but because I have to coninuously listen on COM ports and record the data.
Now during normal operation this is not a problem because it does what it should. Now it can happen though that the user wants to close the application before the measurement is done and the threads are still running.
I got that working too, however I'm not sure if my approach is the right one on that.
I don't want to use terminate since I'm not sure what happends to COM ports that are not closed properly and other things.
Since it can take a while for the threads to close (even on my simple loop thread it takes a few seconds) I wanted to show some little countdown to tell the user yes the shutdown is happening, it just takes a few seconds.
I tried to show it in a QtextEdit in the main widget. But I think that does not work because I'm already in the closeEvent in my approach. I didn't try an additional QDialog popup yet, but I guess it won't work either due to the same resons.
Is there a way to make something like this work to prevent the user from thinking that the program has crashed?
So this is how far I've gotten:
My worker object:
class Thread_Simulator(QtCore.QObject):
sigFin = QtCore.Signal()
sigText = QtCore.Signal(str)
def __init__(self):
super().__init__()
self.quit = 0
def run(self):
i = 0
while i < 10:
i += 1
self.sigText.emit(str(i))
if self.quit == 1:
#self.sigText.emit(__class__+" break")
break
sleep(2)
self.sigFin.emit()
def end(self):
self.quit = 1
Where I start the thread:
self.thread_test = QtCore.QThread()
self.worker_test = Thread_Simulator()
self.worker_test.moveToThread(self.thread_test)
self.thread_test.started.connect(self.worker_test.run)
self.worker_test.sigFin.connect(self.thread_test.quit)
self.worker_test.sigFin.connect(self.worker_test.deleteLater)
self.thread_test.finished.connect(self.thread_test.deleteLater)
self.worker_test.sigText.connect(self.simulator_update)
self.thread_test.start()
And finally my close event of the main application which happens when the user presses the x button I guess.
def closeEvent(self, event):
self.worker_test.end()
print("EXITING -- Waiting for Threads to shut down")
self.thread_test.wait(10000)
return super().closeEvent(event)
Another problem with this is also that if the thread is not running anymore there will be an error on close because the thread is already deleted. I might get around this by setting another variable with the finished signal of the thread and checking for that.
Maybe there is a better way to do this though. Also enabling me to show this countdown thing I thought of.
EDIT:
ok I tried things and it might be stupid but it seems to work this way. I thought that I might as well keep the thread and the worker alive until the end of the program since they don't change anyway and might get restarted. So if I keep them alive it might use up more memory but it is also quicker to restart the worker since they are still alive.
So I removed the deleteLater signal connections and call them manually in the closeEvent instead:
self.thread_test = QtCore.QThread()
self.worker_test = Thread_Simulator()
self.worker_test.moveToThread(self.thread_test)
self.thread_test.started.connect(self.worker_test.run)
self.worker_test.sigFin.connect(self.thread_test.quit)
self.worker_test.sigFin.connect(self.show_finished)
#self.worker_test.sigFin.connect(self.worker_test.deleteLater)
#self.thread_test.finished.connect(self.thread_test.deleteLater)
self.worker_test.sigText.connect(self.simulator_update)
self.thread_test.start()
I'm still not able to show some sort of message to inform the user about the shutdown process (the .append does not show anymore). The shutdown seems to work a little faster though and the "finised" signal gets still correctly emited by the worker.
def closeEvent(self, event): #: QtGui.QCloseEvent) -> None:
self.log.append("EXITING -- Waiting for Threads to shut down")
print("EXITING -- Waiting for Threads to shut down")
self.worker_test.end()
self.thread_test.quit()
self.worker_test.deleteLater()
self.thread_test.deleteLater()
self.thread_test.wait(10000)
return super().closeEvent(event)
I still don't really know if that is the correct way to do it, but it works that way for me.
I'm initializing an additional variable (self.end = None) in the init of the main window.
My closeEvent looks like this now:
def closeEvent(self, event):
if self.thread_test.isRunning():
print("Thread still running -- closing")
self.worker_test.end()
self.end = True
self.show_shutdown_message()
event.ignore()
elif self.thread_test.isFinished():
print("Thread finished -- ENDE")
self.worker_test.deleteLater()
self.thread_test.deleteLater()
self.thread_test.wait(10000)
event.accept()
the Slot of the sigFin signal of the worker thread checks for that and if it was set to anything else but none it will go to the closeEvent again after the worker has finished.
#QtCore.Slot()
def show_finished(self):
print("Worker finished")
if self.end is not None:
self.close()
And with the call of self.show_shutdown_message() I can also show a little QDialog window which informs the user about what is happening.
If anyone knows a more "official" way to do this I'm still open for suggestions though :)
A part of what I am doing, needs me to have background music in a tkinter game I created long time ago. I am using playsound.playsound{ Docs Here } to play music . I could use any other tool if needed to achieve what I intend like pyglet.media or pygame.mixer.
As the actual program was about 1k lines, I have tried adding an MCVE below.
Desired behavior & issue
The BGM (Background Music) should start when the app is launched - of course alongside a GUI, I would have a button to stop the BGM OR more preferably pause/play - I do think I need to use anything other than playsound.playsound for pause/play behavior.
The issue:: I can't seem to figure out how to make that communication between both the threads so that I can stop the music from playing or perhaps terminate the thread playing BGM - I could create a new thread when needed.
What I Have Tried
First up, I created two classes for GUI and BGM, each inheriting from threading.Thread - overridden the constructor and run() methods to do what I intend. Like this -
import tkinter as tk
import threading
from playsound import playsound
class BGM(threading.Thread):
def __init__(self, stopSignal):
threading.Thread.__init__(self)
self.stopSignal = stopSignal
def run(self):
while not self.stopSignal:
playsound('LoL.mp3') # to make this function play asynchronously, pass in False as second arg.
class GUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.root = tk.Tk()
def run(self):
tk.Button(self.root, text='stop').pack()
if __name__ == '__main__':
guiThread = GUI()
guiThread.start()
musicThread = BGM(0)
musicThread.start()
guiThread.root.mainloop()
But as you can see, it will just continue to play the song infinitely as long as the app is alive. There is no communication ( or active sync ) between GUI ( the button to stop song ) and BGM thread.
Now I tried using threading.Event() objects. The official docs on threading.Event() quotes:
This is one of the simplest mechanisms for communication between
threads: one thread signals an event and other threads wait for it.
An event object manages an internal flag that can be set to true with
the set() method and reset to false with the clear() method. The
wait() method blocks until the flag is true.
I'm not sure if this is useful for me to achieve what I intend but using threading.Event() did not do anything for me (or maybe I wasn't able to make use of it properly)
Now
What would you suggest to do to implement a BGM thread which can be stopped ( or paused/played preferably ) with a button(s) in the GUI thread.
Thank You For Any help Or Suggestions :)
I'm relatively new to wxPython (but not Python itself), so forgive me if I've missed something here.
I'm writing a GUI application, which at a very basic level consists of "Start" and "Stop" buttons that start and stop a thread. This thread is an infinite loop, which only ends when the thread is stopped. The loop generates messages, which at the moment are just output using print.
The GUI class and the infinite loop (using threading.Thread as a subclass) are held in separate files. What is the best way to get the thread to push an update to something like a TextCtrl in the GUI? I've been playing around with PostEvent and Queue, but without much luck.
Here's some bare bones code, with portions removed to keep it concise:
main_frame.py
import wx
from loop import Loop
class MainFrame(wx.Frame):
def __init__(self, parent, title):
# Initialise and show GUI
# Add two buttons, btnStart and btnStop
# Bind the two buttons to the following two methods
self.threads = []
def onStart(self):
x = Loop()
x.start()
self.threads.append(x)
def onStop(self):
for t in self.threads:
t.stop()
loop.py
class Loop(threading.Thread):
def __init__(self):
self._stop = threading.Event()
def run(self):
while not self._stop.isSet():
print datetime.date.today()
def stop(self):
self._stop.set()
I did, at one point, have it working by having the classes in the same file by using wx.lib.newevent.NewEvent() along these lines. If anyone could point me in the right direction, that'd be much appreciated.
The easiest solution would be to use wx.CallAfter
wx.CallAfter(text_control.SetValue, "some_text")
You can call CallAfter from any thread and the function that you pass it to be called will be called from the main thread.
I have a PyQt program, in this program I start a new thread for drawing a complicated image.
I want to know when the thread has finished so I can print the image on the form.
The only obstacle I'm facing is that I need to invoke the method of drawing from inside the GUI thread, so I want a way to tell the GUI thread to do something from inside the drawing thread.
I could do it using one thread but the program halts.
I used to do it in C# using a BackgroundWorker which had an event for finishing.
Is there a way to do such thing in Python? or should I hack into the main loop of PyQt application and change it a bit?
In the samples with PyQt-Py2.6-gpl-4.4.4-2.exe, there's the Mandelbrot app. In my install, the source is in C:\Python26\Lib\site-packages\PyQt4\examples\threads\mandelbrot.pyw. It uses a thread to render the pixmap and a signal (search the code for QtCore.SIGNAL) to tell the GUI thread its time to draw. Looks like what you want.
I had a similar issue with one of my projects, and used signals to tell my main GUI thread when to display results from the worker and update a progress bar.
Note that there are several examples to connect objects and signals in the PyQt reference guide. Not all of which apply to python (took me a while to realize this).
Here are the examples you want to look at for connecting a python signal to a python function.
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig"), pyFunction)
a.emit(QtCore.SIGNAL("pySig"), "Hello", "World")
Also, don't forget to add __pyqtSignals__ = ( "PySig", ) to your worker class.
Here's a stripped down version of what I did:
class MyGui(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.worker = None
def makeWorker(self):
#create new thread
self.worker = Worker(work_to_do)
#connect thread to GUI function
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('progressUpdated'), self.updateWorkerProgress)
QtCore.QObject.connect(self.worker, QtCore.SIGNAL('resultsReady'), self.updateResults)
#start thread
self.worker.start()
def updateResults(self):
results = self.worker.results
#display results in the GUI
def updateWorkerProgress(self, msg)
progress = self.worker.progress
#update progress bar and display msg in status bar
class Worker(QtCore.QThread):
__pyqtSignals__ = ( "resultsReady",
"progressUpdated" )
def __init__(self, work_queue):
self.progress = 0
self.results = []
self.work_queue = work_queue
QtCore.QThread.__init__(self, None)
def run(self):
#do whatever work
num_work_items = len(self.work_queue)
for i, work_item in enumerate(self.work_queue):
new_progress = int((float(i)/num_work_items)*100)
#emit signal only if progress has changed
if self.progress != new_progress:
self.progress = new_progress
self.emit(QtCore.SIGNAL("progressUpdated"), 'Working...')
#process work item and update results
result = processWorkItem(work_item)
self.results.append(result)
self.emit(QtCore.SIGNAL("resultsReady"))
I believe that your drawing thread can send an event to the main thread using QApplication.postEvent. You just need to pick some object as the receiver of the event. More info
Expanding on Jeff's answer: the Qt documentation on thread support states that it's possible to make event handlers (slots in Qt parlance) execute in the thread that "owns" an object.
So in your case, you'd define a slot printImage(QImage) on the form, and a doneDrawing(QImage) signal on whatever is creating the image, and just connect them using a queued or auto connection.