I tried using self.terminate() in the QThread class, and also self.thread.terminate() in the GUI class. I also tried putting self.wait() in both cases. However, there are two scenarios that happen:
1) The thread does not terminate at all, and the GUI freezes waiting for the thread to finish. Once the thread finished, the GUI unfreezes and everything is back to normal.
2) The thread indeed does terminate, but at the same time it freezes the entire application.
I also tried using self.thread.exit(). No joy.
To further clarify, I am trying to implement a user-abort button in GUI which would terminate the executing of the thread at any point in time.
Thanks in advance.
EDIT:
Here is the run() method:
def run(self):
if self.create:
print "calling create f"
self.emit(SIGNAL("disableCreate(bool)"))
self.create(self.password, self.email)
self.stop()
self.emit(SIGNAL("finished(bool)"), self.completed)
def stop(self):
#Tried the following, one by one (and all together too, I was desperate):
self.terminate()
self.quit()
self.exit()
self.stopped = True
self.terminated = True
#Neither works
And here is the GUI class' method for aborting the thread:
def on_abort_clicked(self):
self.thread = threadmodule.Thread()
#Tried the following, also one by one and altogether:
self.thread.exit()
self.thread.wait()
self.thread.quit()
self.thread.terminate()
#Again, none work
From the Qt documentation for QThread::terminate:
Warning: This function is dangerous and its use is discouraged. The
thread can be terminated at any point in its code path. Threads can be
terminated while modifying data. There is no chance for the thread to
clean up after itself, unlock any held mutexes, etc. In short, use
this function only if absolutely necessary.
It's probably a much better idea to re-think your threading strategy such that you can e.g. use QThread::quit() to signal the thread to quit cleanly, rather than trying to get the thread to terminate this way. Actually calling thread.exit() from within the thread should do that depending on how you have implemented run(). If you'd like to share the code for your thread run method that might hint as to why it doesn't work.
This is what I did:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
stop_flag = 1
...
#=========500 ms class ===================
class Timer500msThread(QThread):
signal_500ms = pyqtSignal(str)
....
def timer500msProcess(self):
if MainWindow.stop_flag == 0 :
self.timer_500ms.stop()
#==========
#=========Main Window ===================
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
MainWindow.stop_flag=0 #this sets the flag to 0 and when the next 500ms triggers the
#the thread ends
print("Program Ending")
I had a similar problem and solved it with use of pyqtSignals and pyqtSlots.
You could create a pyqtSignal in your MainWindow-class and use the aboutToQuit-function to your QApplication instance.
Then you connect this aboutToQuit-function with another which emit a signal to the slot in your separate thread.
Then you can define a stop() function in this Thread which runs if the signal is emitted.
In this case the thread would not terminate during its work.
MainWindow:
class mainSignals(QObject):
isClosed = pyqtSignal()
class mainwindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(mainwindow, self).__init__()
self.mainSignal = mainSignals()
...
...
if __name__ = "__main__":
# This function runs if the application is closed.
# (app.aboutToQuit.connect(windowClose))
def windowClose():
window.mainSignal.isClosed.emit() # Emit the signal to the slot in (sepThread)
app = QApplication(sys.argv)
app.aboutToQuit.connect(windowClose)
window = mainwindow()
window.show()
sys.exit(app.exec())
sepThread:
class sepThread(QRunnable):
def __init__(self, parent):
super(sepThread,self).__init__()
self._parent = parent
self.mainOpen = True
self._parent.mainSignal.isClosed.connect(self.stopThread)
# If the signal was emitted by the Mainapplication
# the stopThread function runs and set the mainOpen-attribute to False
def stopThread(self):
self.mainOpen = False
def run(self):
while self.mainOpen == True:
# Do something in a loop while mainOpen-attribute is True
...
...
Related
I'm trying to understand which of the following approaches is better. I'll use PyQt5 to illustrate the example, as that's the system I'm working with.
We have a Class ControlWidget(QWidget) that provides UI components and spawns helper threads that are just workers executing some task. If one of the workers experiences a failure, we want to notify the parent so it can visually update the user and do some other things like cleanup/disable user input.
Approach 1 would be to send an event from the worker that encounters the error and catch it in the parent instance. The worker Class has an errorSignal that simply indicates the presence of an error.
# The worker class
Class Worker(QObject, Thread):
errorSignal = pyqtSignal(bool)
def __init__(self):
super().__init__()
self._running = True
def run(self):
while self._running:
try:
# do some work here that might fail
except:
# notify parent that there was an error
errorSignal.emit(True)
self._running = False
def terminate(self):
self._running = False
# The parent class
class ControlWidget(QWidget):
def __init__(self):
super().__init__()
# instantiate the workers
self.worker1 = Worker()
self.worker2 = Worker()
# connect to the error signal
self.worker1.errorSignal.connect(self.handle_error)
self.worker2.errorSignal.connect(self.handle_error)
# start the workers
self.worker1.start()
self.worker2.start()
def handle_error(self, error):
if error:
# clean up worker threads
self.worker1.terminate()
self.worker1.join()
self.worker2.terminate()
self.worker2.join()
# notify user
self.setStyleSheet('background: red;')
# disable UI input etc
self.button.setEnabled(False)
...
Approach 2 would be to make an error_status property on the parent, and pass a reference to each child worker, so it can toggle the error bit directly.
# The worker class
Class Worker(QObject, Thread):
def __init__(self, parent):
super().__init__()
self._running = True
self.parent = parent
def run(self):
while self._running:
try:
# do some work here that might fail
except:
# notify parent that there was an error
self.parent.error_status = True
def terminate(self):
self._running = False
# The parent class
class ControlWidget(QWidget):
def __init__(self):
super().__init__()
# instantiate the workers
self.worker1 = Worker(parent=self)
self.worker2 = Worker(parent=self)
# no signals here :)
# start the workers
self.worker1.start()
self.worker2.start()
#property
def error_status(self):
return self._error_status
#error_status.setter
def error_status(self, value):
self._error_status = value
self.handle_error(value)
def handle_error(self, error):
if error:
# clean up worker threads
self.worker1.terminate()
self.worker1.join()
self.worker2.terminate()
self.worker2.join()
# notify user
self.setStyleSheet('background: red;')
# disable UI input etc
self.button.setEnabled(False)
...
Events per second limits?
Functionally both approaches will work, however, I suppose I'm worried that if I have a lot of events in my application (I do) I'll overload the event queue and lock up or miss some. I read somewhere that Qt has a limit of about 2 million events per second (although now I can't find the reference). It may just depend on available memory. If I may conceivably reach this limit, isn't it better to try to offload some event handling where possible?
Is approach 2 a better way to handle this type of issue (presuming I've addressed the thread safety issue with my edit)? It does feel slightly gross to pass references to parent/child objects all over the place.
Perhaps this is 6 of one, a half dozen of the other? Any feedback would be appreciated.
Events cannot be missed, they are all processed and eventually discarded if not handled and/or ignored (which are two related but different things).
Both your approaches have flaws, though.
Signals
You should not use terminate() (the docs clearly say that it's dangerous and its use is discouraged).
QThread also doesn't have a join() method, it has wait() which does the same, but you should not use it in the main thread: Qt automatically calls the connected slot in the receiver's thread, and since the signal is connected to handle_error (which is a member of an object in the main thread) the result is that it will block the main thread until the thread has actually finished execution. To avoid that, wait() must be executed in the worker thread, so you can eventually consider a two-signal approach: first react to the error signal, then do something until the thread's finished() signal is finally emitted.
Flag setter
Doing this would only partially work, since the setter is called from the worker thread, so wait() will be correctly called from there. Unfortunately, you're also trying to access the UI in the same function, which is forbidden since UI elements are not thread safe. Best case scenario, you'll have some graphic artifacts and some widgets won't be properly updated; worst (and more likely, especially due to setStyleSheet()) case, the program will crash.
Signals are the only proper and safe way to communicate with the main thread, since they allow correct event handling and prevent freezing of the UI while waiting for the thread to quit.
If you need to change something in the UI when something else happens in a thread, create a function in an object that resides in the main thread and connect it to the appropriate signal that will be emitted from the other thread. If you need to do something that is potentially blocking while waiting for the thread, do it in that thread.
I'm posting an answer to my own here based on helpful comments from #ekhumoro and #musicamante, as well as some further digging I've done.
My main pushback is that it seems like the suggestions so far maintain that the only way to communicate data between threads is via signals. While this may be the best choice in many/most cases, and a good rule of thumb, there are other safe ways to do it. The reason I continue to beat this possibly dead horse is that in the Qt documentation it mentions a limit on the number of signals:
On an i586-500, you can emit around 2,000,000 signals per second connected to one receiver, or around 1,200,000 per second connected to two receivers.
Granted the hardware suggested here is dated and the current Qt version documentation makes no such reference. However, signals do impart additional overhead vs a normal callback (this is noted in both past and present versions of Qt docs). While this limit is unlikely to be reached in my case, I'm now more interested in understanding both approaches merits and drawbacks.
I think both approaches can be used in a thread-safe manner, although that wasn't properly addressed in the original question. Both approaches are posted here with full MWE.
Approach 1: Using Signals
# thread-signal.py
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout
from threading import Thread
import time
# The worker class
class Worker(QObject, Thread):
errorSignal = pyqtSignal(bool)
def __init__(self, id=""):
super().__init__()
self.id = id
self._running = True
def run(self):
while self._running:
try:
# do some work here that might fail
time.sleep(2)
raise ValueError(f"Worker Failure")
except:
# notify parent that there was an error
print(f"Worker {self.id} failed")
self.errorSignal.emit(True)
self._running = False
def terminate(self):
self._running = False
# The parent class
class ControlWidget(QWidget):
def __init__(self):
super().__init__()
# set up UI
vbox = QVBoxLayout()
self.button = QPushButton("Test")
vbox.addWidget(self.button)
self.setLayout(vbox)
# instantiate the workers
self.worker1 = Worker(id=1)
self.worker2 = Worker(id=2)
# connect to the error signal
self.worker1.errorSignal.connect(self.handle_error)
self.worker2.errorSignal.connect(self.handle_error)
# start the workers
self.worker1.start()
self.worker2.start()
def handle_error(self, error):
if error:
# clean up worker threads
self.worker1.terminate()
self.worker2.terminate()
# notify user
self.button.setStyleSheet('background: red;')
# disable UI input etc
self.button.setEnabled(False)
print(f"Worker 1 isAlive = {self.worker1.is_alive()}")
print(f"Worker 2 isAlive = {self.worker2.is_alive()}")
if __name__=='__main__':
app = QApplication([])
window = QMainWindow()
window.show()
ctrl = ControlWidget()
window.setCentralWidget(ctrl)
# window.setLayout(layout)
app.exec()
The result of running the example is that after 2 seconds, the workers fail (Worker 1 fails first because it was started first - although this may not always be the case depending on how the GIL is shared between threads). In the end, both workers are shutdown and the control widget is deactivated.
$ python thread-signal.py
Worker 1 failed
Worker 1 isAlive = False
Worker 2 isAlive = True
Worker 2 failed
Worker 1 isAlive = False
Worker 2 isAlive = False
Approach 2: Using parent References
# thread-reference.py
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout
from threading import Thread, Lock
import time
# global thread lock
lock = Lock()
# The worker class
class Worker(Thread):
def __init__(self, id="", parent=None):
super().__init__()
self.id = id
self.parent = parent
self._running = True
def run(self):
while self._running:
try:
# do some work here that might fail
time.sleep(2)
raise ValueError(f"Worker Failure")
except:
with lock:
# notify parent that there was an error
print(f"Worker {self.id} failed")
self._running = False
self.parent.error_status = True
def terminate(self):
self._running = False
# The parent class
class ControlWidget(QWidget):
def __init__(self):
super().__init__()
# set up UI
vbox = QVBoxLayout()
self.button = QPushButton("Test")
vbox.addWidget(self.button)
self.setLayout(vbox)
# instantiate the workers
self.worker1 = Worker(id=1, parent=self)
self.worker2 = Worker(id=2, parent=self)
# start the workers
self.worker1.start()
self.worker2.start()
def handle_error(self, error):
if error:
# clean up worker threads
self.worker1.terminate()
self.worker2.terminate()
# notify user
self.button.setStyleSheet('background: red;')
# disable UI input etc
self.button.setEnabled(False)
print(f"Worker 1 isAlive = {self.worker1.is_alive()}")
print(f"Worker 2 isAlive = {self.worker2.is_alive()}")
#property
def error_status(self):
return self._error_status
#error_status.setter
def error_status(self, value):
self._error_status = value
self.handle_error(value)
if __name__=='__main__':
app = QApplication([])
window = QMainWindow()
window.show()
ctrl = ControlWidget()
window.setCentralWidget(ctrl)
app.exec()
The end result is similar in this case, the workers are shut down and the control widget deactivated, but inspection of the output shows an interesting difference:
$ python thread-reference.py
Worker 1 failed
Worker 1 isAlive = True
Worker 2 isAlive = True
Worker 2 failed
Worker 1 isAlive = False
Worker 2 isAlive = True
The isAlive() value of the thread doesn't resolve to False until after the handle_error method is complete. I'm guessing this is just the way the bytecode execution comes out due to the GIL.
I got a project that listens to the microphone if I ever press start. I am so new to pyqt5 so I don't really know Qthreading that much, plus most of the examples are progress bars and for loops. I want that when it presses start it runs the while loop of recording audio for 10 seconds, identifying it, and doing again. And that it only stops when I press stop, a variable is now False. Now this is what I came up with. I am sorry if it looks dumb because I don't really know that much from Qthread. I just want to be able to run this one in particular.
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.is_Played = True
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
while self.is_Played == True:
listen()
time.sleep(0.01)
def stopClicked(self):
self.is_Played = False
self.finished.emit()
if __name__ == "__main__":
app = QApplication(sys.argv)
form = AvisoWindow()
threading.Thread(target=form.__init__(), daemon=True).start()
form.show()
app.exec_()
First of all, nothing related to the UI shall ever be run or accessed in an external thread. Blocking functions and while loops (unless it's guaranteed that they exit almost instantly) should also be avoided, as they block the event loop preventing proper redrawing of the UI and user interaction.
Even assuming that directly using threads with UI was possible, your attempt has two important problems:
__init__() should never be called externally (and, even in that case, it expects at least the instance as argument);
the target argument of Thread expects a callable (a reference to a function), but you're already calling the method (using the parentheses), so it would use the returned value of that called function (which is usually None for an init);
In order to use threads that have to react to an UI environment, in Qt you should create a subclass of QThread that acts as your "worker".
class Worker(QThread):
result = pyqtSignal(str)
def run(self):
self.keepRunning = True
while self.keepRunning:
res = someFunctionThatReturnsAString()
self.result.emit(res)
time.sleep(0.01)
def stop(self):
self.keepRunning = False
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.worker = Worker()
self.worker.result.connect(self.resultReceived)
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
if not self.worker.isRunning():
self.worker.start()
def stopClicked(self):
self.worker.stop()
def resultReceived(self, result):
print(result)
I have the following pyqtmain.py:
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine() # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And another thread script performing the actual measurement:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self):
QObject.__init__(self) # Don't forget to call base class constructor
#pyqtSlot()
def run(self):
self.measure_msg.emit('phase1')
time.sleep(2) # here I would like to make it as an interrupt
self.measure_msg.emit('phase2')
What this code does now is that after the Start button is pressed, the function run in the thread will be executed. However, actually in the function run, there are two phases of the measurement. Right now I used an time delay.
But what I would like to implement actually is that after the 'phase1' measurement is done. A message box will be popped up, and at the same time, the thread will be paused/held. Until the user closed the message box, then the thread function will be resumed.
Use a QWaitCondition from the QtCore module. Using a mutex lock, you set the background thread to wait/sleep until the foreground thread wakes it back up. Then it will continue doing its work from there.
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mutex = QMutex()
self.cond = QWaitCondition()
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine(self.mutex, self.cond) # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
# since this is a slot, it will always get run in the event loop in the main thread
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
msgBox = QMessageBox(parent=self)
msgBox.setText("Close this dialog to continue to Phase 2.")
msgBox.exec_()
self.cond.wakeAll()
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self, mutex, cond):
QObject.__init__(self) # Don't forget to call base class constructor
self.mtx = mutex
self.cond = cond
#pyqtSlot()
def run(self):
# NOTE: do work for phase 1 here
self.measure_msg.emit('phase1')
self.mtx.lock()
try:
self.cond.wait(self.mtx)
# NOTE: do work for phase 2 here
self.measure_msg.emit('phase2')
finally:
self.mtx.unlock()
Your timing is a little bit off in all this though. You create the app and start the thread before you even show your window. Thus, the message box will pop up before the main window even pops up. To get the right sequence of events, you should start your thread as part of the run method of your MainWindow, after you have already made the main window visible. If you want the wait condition to be separate from the setting of the messages, you may need a separate signal and slot to deal with that.
You can't display a QDialog from within a QThread. All GUI related stuff must be done in the GUI thread (the one that created the QApplication object). What you could do is to use 2 QThread:
1st: perform phase1. You can connect the finished signal of this QThread to a slot in the QMainWindow that will display the popup (using QDialog.exec_() so it will be modal).
2nd: perform phase2. You create the QThread after the popup shown here above has been closed.
Your thread can emit a signal to the main window to show the dialog.
If you don't want to close the thread while the dialog is open, the thread could enter a while loop for waiting. In the while loop it can continuously check a variable which the main thread can set to true after the dialog is finished.
This might not be the cleanest solution, but it should work.
To clarify my answer a bit, I added some pseudo code. What you have to care about is how you share the dialog_closed variable. You could e.g. use a member variable of the thread class.
Thread:
emit_signal
dialog_closed = False
while not dialog_closed:
pass
go_on_with_processing
MainThread:
def SignalRecieved():
open_dialog
dialog_closed = True
I recently had to solve pretty much this problem, did a little research and discovered an elegant technique that seems to work reliably. I didn't need the full complexity detailed there, so here's an outline of the steps I took.
My GUI class defines, as class attributes, two signals.
oyn_sig = pyqtSignal(str) # Request for operator yes/no
ryn_sig = pyqtSignal(bool) # Response to yes/no request
Inside the method that initialises the GUI components this signal is connected to the GUI instance's signal handler.
self.oyn_sig.connect(self.operator_yes_no)
Here's the code for the handler method of the GUI:
#pyqtSlot(str)
def operator_yes_no(self, msg):
"Asks the user a `yes/no question on receipt of a signal then signal a bool answer.`"
answer = QMessageBox.question(None,
"Confirm Test Sucess",
msg,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
# Signal the caller that the result was received.
self.ryn_sig.emit(answer==QMessageBox.Yes)
As usual the GUI is running in the main thread, and so it needs to be signalled from the thread doing the work in the background. In turn, once it's received the operator's response it raises a response signal to the originating thread.
The worker thread uses the following function to get an operator response.
def operator_yes_no(self, msg):
loop = LoopSpinner(self.gui, msg)
loop.exec_()
return loop.result
This creates a LoopSpinner object and starts executing its event loop, thereby suspend the current thread's event loop until the "inner thread" terminates. Most of the smarts are hidden inside the LoopSpinner class, which should probably have been better named. Here's its definition.
class LoopSpinner(QEventLoop):
def __init__(self, gui, msg):
"Ask for an answer and communicate the result."
QEventLoop.__init__(self)
gui.ryn_sig.connect(self.get_answer)
gui.oyn_sig.emit(msg)
#pyqtSlot(bool)
def get_answer(self, result):
self.result = result
self.quit()
A LoopSpinner instance connects the response signal to its get_answer method and emits the question signal. When the signal is received the answer is stored as an attribute value and the loop quits. The loop is still referenced by its caller, which can safely access the result attribute before the instance is garbage collected.
After read and searching I am trying to use the generate a QObject then use the movetoThread method to run an independent process and allow the QMainWindow to continue to respond. This has not worked when I have tried to implement the operation in a QThread.run() method. The following code is my attempt to make a simple example. While the code works in running thread independent of the MainWindow, it does not abort. The only way I can get a thread to stop is to set worker.end = True. Which I think should not be the way to do it.
"""
This is a program to test Threading with Objects in PyQt4.
"""
from time import sleep
import sys
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from PyQt4.QtGui import QMainWindow, QApplication, QProgressBar
from PyQt4.QtGui import QPushButton, QVBoxLayout, QWidget
class workerObject(QObject):
bar_signal = pyqtSignal(int)
res_signal = pyqtSignal(str)
term_signal = pyqtSignal()
def __init__(self, maxIters):
super(workerObject, self).__init__()
self.maxIters = maxIters
def run(self):
self.bar_signal.emit(self.maxIters)
sleep(1)
self.end = False
for step in range(self.maxIters):
if self.end:
self.maxIters = step
break
self.bar_signal.emit(step)
sleep(2)
self.res_signal.emit("Got to {}".format(self.maxIters))
self.term_signal.emit()
#pyqtSlot()
def mystop(self):
print "stop signalled?"
self.end = True
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
self.maxIters = 50
widget = QWidget()
layout = QVBoxLayout(widget)
self.go_btn = QPushButton()
self.go_btn.setText('Go')
layout.addWidget(self.go_btn)
self.abort_btn = QPushButton()
self.abort_btn.setText('Stop')
layout.addWidget(self.abort_btn)
self.simulation_bar = QProgressBar()
self.simulation_bar.setRange(0, self.maxIters)
self.simulation_bar.setFormat("%v")
layout.addWidget(self.simulation_bar)
self.setCentralWidget(widget)
self.go_btn.clicked.connect(self.run_mc)
# The button calls the windows method to stop --- it could
# be that is 'clicked' calls the worker.mystop
# self.abort_btn.clicked.connect(self.stop_mc)
# This allows for the abort button to do somethign in the MainWindow
# before the abort_signal is sent, this works
self.abort_btn.clicked.connect(self.stop_mc)
def run_mc(self):
self.thread = QThread()
self.worker = workerObject(self.maxIters)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
# This is the simple stop method, but does not work
# self.abort_btn.clicked.connect(self.worker.mystop)
# This uses the signal in the MCwindow - this connection does NOT works
self.abort_signal.connect(self.worker.mystop)
# This does NOT stop the thread
# and would not allow for any clean up in the worker.
# self.abort_signal.connect(self.thread.terminate)
# This is a 'bad' way to stop the woker ... It does, however, work
# self.abort_signal.connect(self.stopper)
self.worker.bar_signal.connect(self.setBar)
self.worker.res_signal.connect(self.setData)
self.worker.term_signal.connect(self.thread.terminate)
self.thread.start()
def stop_mc(self):
print "Stopping?!"
# This signal is NEVER seen by the Worker.
self.abort_signal.emit()
def stopper(self):
print "I should stop?!"
# Should use signals to tell the worker to stop - and not setting a attribute
self.worker.end=True
#pyqtSlot(int)
def setBar(self, val):
self.simulation_bar.setValue(val)
#pyqtSlot(str)
def setData(self, txt):
print "Got done Sig!", txt
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MCwindow()
window.show()
sys.exit(app.exec_())
The reason why the slot connected to abort_signal doesn't seem to get called, is because cross-thread signals are queued by default. This means the signal will be wrapped as an event and posted to the event queue of whichever thread the receiver is living in.
In your particular example, the receiver is a worker object which has been moved to a worker thread. Calling start() on the worker thread will start its event-loop, and that is where abort_signal will be queued. However, the run() method of the worker object starts a for loop, which will block the thread's event processing in exactly the same way it would if it was executed in the main gui thread!
You can more clearly see what's happening if you make a few adjustments to your example:
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
# use a sane default
self.maxIters = 5
...
# DO NOT use QThread.terminate
self.worker.term_signal.connect(self.thread.quit)
Now run the example, and then click the Go button, click the Stop button, and wait for the worker to complete normally. This should produce output like this:
Stopping?!
Got done Sig! Got to 5
stop signalled?
Note that "stop signalled" is output last - i.e. after run() exits and control has returned to the thread's event-loop. In order to process in-coming signals while the worker is running, you will need to force immediate processing of the thread's pending events. This can be done like this:
for step in range(self.maxIters):
QApplication.processEvents()
...
With that in place, you should then see output like this:
Stopping?!
stop signalled?
Got done Sig! Got to 2
Which is presumably what you intended.
Typically a thread will close when it exits the run method. The other way to get a regular python thread to close is by calling it's join method.
For PyQt the join method should either be the quit or terminate method. You should probably still set your end variable to True.
First off, I'm very new to Python and Pyside. In order to do a bit of self-improvement, I'm trying to get a QTimer to execute every second in a child thread of my PySide program (at the moment I just want it to print "hi!" to a terminal every second without freezing the main window).
I tried converting the example I found on the Qt Wiki from C++ to Python/PySide, but since I don't really know C++ I assume I converted it incorrectly and that's why it's not working properly.
At the moment, the doWork() function only seems to execute once and then never again. What am I doing wrong? Is there a better way to execute a function every second in PySide without freezing the main window?
Here's the code (I have removed some main window code to increase clarity):
from PySide import QtGui
from PySide import QtCore
from client_gui import Ui_MainWindow
statsThread = QtCore.QThread()
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
#setup GUI
self.setupUi(self)
#start thread to update GUI
self.statsThread = updateStatsThread()
self.statsThread.start(QtCore.QThread.TimeCriticalPriority)
class updateGuiWithStats(QtCore.QObject):
def Worker(self):
timer = QtCore.QTimer()
timer.timeout.connect(self.doWork())
timer.start(1000)
def doWork(self):
print "hi!"
class updateStatsThread (QtCore.QThread):
def run(self):
updater = updateGuiWithStats()
updater.Worker()
self.exec_()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
sys.exit(app.exec_())
#Masci already pointed out the fix you needed for your timer.timeout.connect, but I see more issues than just that.
No need to create a global QThread that is never used:
statsThread = QtCore.QThread()
Your QTimer is being garbage collected right away because its created without a parent, and you aren't saving it within your class. This is why even after you fix your timer connection, it will still not work... Try:
class UpdateGuiWithStats(QtCore.QObject):
def startWorker(self):
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.doWork)
self.timer.start(1000)
Also, use UpperCase for the first letter of classes, and camelCase for methods. You are doing a mixture of both ways.
A couple of notes based on that link you provided, your example, and other comments on here... You can use just a QTimer as a solution if your doWork() is very light and will not block your main event loop with a bunch of data crunching, sleeping, etc. If it does, then doWork() will need to be moved to a QThread, as your example is doing. But at that point it is somewhat unnecessary to use an event loop, and a QTimer in a separate class that calls its own work. This all could be consolidated into a single class, something like:
class UpdateStatsThread(QtCore.QThread):
def __init__(self, parent=None):
super(UpdateStatsThread, self).__init__(parent)
self._running = False
def run(self):
self._running = True
while self._running:
self.doWork()
self.msleep(1000)
def stop(self, wait=False):
self._running = False
if wait:
self.wait()
def doWork(self):
print "hi!"
in updateGuiWithStats class, Worker method:
timer.timeout.connect(self.doWork())
should be
timer.timeout.connect(self.doWork)
You are connecting timeout signal to None (the return value of doWork() method), and I think this is why it is executed only once: doWork is called during the connection and nomore. When you make connections, remember to connect the function name (in Pythonics words, the callable object) and not the function call.
By the way, even if the above solved your problem, you should avoid using threads since QTimer already does by its own you need. In the docs you linked, the first answer to the When shouldn’t I use threads? question is: Timers.