I'm currently trying to build a PyQt5 app and it should consist of the main GUI and in the background there should be a different thread that should measure something in an infinite loop. And I want to start and stop this thread using a QAction or Checkbox.
So to say when I press the checkbox and the state is true the thread should be started and if I click it again it should be stopped.
Now what is the best way to implement this?
Currently I'm using a Worker thread like this:
class Worker(QtCore.QObject):
def __init__(self):
super(Worker, self).__init__()
self._isRunning = True
def task(self):
if not self._isRunning:
self._isRunning = True
while self._isRunning:
time.sleep(0.5)
... measure ...
def stop(self):
self._isRunning = False
and this in the main thread to make it run:
self.thread = QtCore.QThread()
self.thread.start()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.btn_start.clicked.connect(self.worker.task)
self.btn_stopped.clicked.connect(lambda: self.worker.stop())
So far this works. But I'm not really confident that this is the best way to do it and I'd also like it much better if I could make the same thing using a checkbox in the way described.
As it stands, the code you've posted is not multi-threaded. This is because worker.task()
is started by code in the main thread, so it will also run in the main thread. You need to use the worker thread's started
signal to start the task, and a custom signal on the worker to quit the thread.
The demo script below should fix these issues:
import sys, time
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
messageSent = QtCore.pyqtSignal(str)
def __init__(self):
super(Worker, self).__init__()
self._isRunning = False
def task(self):
print('WKR thread:', QtCore.QThread.currentThread())
self._isRunning = True
count = 0
while self._isRunning:
time.sleep(0.5)
count += 1
self.messageSent.emit('count: %s' % count)
self.finished.emit()
def stop(self):
self._isRunning = False
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QCheckBox('Test', self)
self.button.toggled.connect(self.handleButton)
self.label = QtWidgets.QLabel(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.thread = QtCore.QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.task)
self.worker.finished.connect(self.thread.quit)
self.worker.messageSent.connect(self.label.setText)
def handleButton(self, checked=False):
print('GUI thread:', QtCore.QThread.currentThread())
if checked:
self.label.clear()
self.thread.start()
else:
self.worker.stop()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(800, 150, 200, 50)
window.show()
sys.exit(app.exec_())