Home > Back-end >  PyQt5: start, stop and pause a thread with progress updates
PyQt5: start, stop and pause a thread with progress updates

Time:05-25

I’ve got some issues with threading in my program. I have a main program and also a GUI-Class, created with PyQt. I’ve created a small example of my problems. When I start the python script and press start, the progress bar is working fine, but I get the following warnings:

QBackingStore::endPaint() called with active painter on backingstore paint device
QObject::setParent: Cannot set parent, new parent is in a different thread

These alarms occur due to different threads. But to be honest, I have no idea how to handle the objects, like this progress bar of the gui-class. I’ve tried many ways but haven’t found a good solution for this. By pressing start, it starts, by pressing stop the progress bar stops. But here is the next issue, mostly my kernel dies when I press stop. I guess it’s connected to the same problem with the threading?!

The reason for the threading is so that the script can still be interacted with while it is running.

Attached are my two python files: my program and also the gui.

MAINPROGRAM:

#MAINPROGRAM

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import threading
import time

class exampleprogram(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
    def __init__(self, parent=None):
        super(exampleprogram, self).__init__(parent)
        self.setupUi(self)
        self.pushButton_Start.clicked.connect(self.start)
        self.pushButton_Stop.clicked.connect(self.stop)
        self.running = False
            
    def start(self):
        if(self.running == False):
            print("Start")
            self.thread = threading.Thread(target=self.run, args=())
            self.thread.start()            
            
    def stop(self):
        print("Stop")
        self.running = False
        
    def run(self):
        self.running = True
        x = 0
        thread = threading.currentThread()
        while getattr(thread, "do_run", True):
            self.thread.do_run = self.running
            if(x == 100):
                thread.do_run = False
            self.progressBar.setValue(x)
            time.sleep(0.1)
            x = x 1
        self.stop()

def main():
    app = QApplication(sys.argv)
    form = exampleprogram()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

GUI:

#GUI

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(573, 92)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 195, 30))
        self.layoutWidget.setObjectName("layoutWidget")
        self.formLayout_3 = QtWidgets.QFormLayout(self.layoutWidget)
        self.formLayout_3.setContentsMargins(0, 0, 0, 0)
        self.formLayout_3.setObjectName("formLayout_3")
        self.pushButton_Start = QtWidgets.QPushButton(self.layoutWidget)
        self.pushButton_Start.setObjectName("pushButton_Start")
        self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.pushButton_Start)
        self.pushButton_Stop = QtWidgets.QPushButton(self.layoutWidget)
        self.pushButton_Stop.setObjectName("pushButton_Stop")
        self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.pushButton_Stop)
        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(230, 20, 311, 23))
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName("progressBar")
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Test Gui"))
        self.pushButton_Start.setText(_translate("MainWindow", "Start"))
        self.pushButton_Stop.setText(_translate("MainWindow", "Stop"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Does anyone have an idea, how to fix it?

EDIT: After stopping the run-function it should be possible to run it again by pressing the start button. Therefore it doesn't matter if it was stopped by the counter x or by pressing the stop button. It's just important not too start multiple times the function.

Many thanks in advance!

Andrew

CodePudding user response:

Whilst it's possible to use Python threads with PyQt, it's generally better to use QThread with a separate worker object, since it more easily allows thread-safe communication between the worker thread and main thread via signals and slots. One of the main problems with your example is precisely that you are attempting to perform GUI-related operations outside of the main thread, which is not supported by Qt (hence the warning messages).

If you want to start, stop and pause the worker from the GUI, it will require some careful handling to ensure the worker and thread shut down cleanly if the user tries to close the window while they are still running. In the example below, I have used a simple abort mechanism in the closeEvent; if you want more sophisticated handling, you could, for example, ignore() the close-event while the worker is still running. Pressing Start will start/contine the worker, and pressing Stop will pause it. Once the worker has finished, pressing Start will completely re-start it (i.e. it will go back to zero):

class Worker(QtCore.QObject):
    progress = QtCore.pyqtSignal(int)
    finished = QtCore.pyqtSignal()

    def __init__(self):
        super().__init__()
        self._paused = True
        self._count = -1

    def start(self):
        self._paused = False
        if self._count < 0:
            print('Start')
            self._run()
        else:
            print('Continue')

    def stop(self, *, abort=False):
        self._paused = True
        if abort:
            print('Abort')
            self._count = -1
        else:
            print('Pause')

    def _run(self):
        self._count = 0
        self._paused = False
        while 0 <= self._count <= 100:
            if not self._paused:
                QtCore.QThread.msleep(100)
                self._count  = 1
                self.progress.emit(self._count)
        self.stop(abort=True)
        self.finished.emit()
        print('Finished')


class exampleprogram(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(exampleprogram, self).__init__(parent)
        self.setupUi(self)
        self.thread = QtCore.QThread(self)
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.start)
        self.worker.finished.connect(self.thread.quit)
        self.worker.progress.connect(self.progressBar.setValue)
        self.pushButton_Start.clicked.connect(self.start)
        self.pushButton_Stop.clicked.connect(self.stop)

    def start(self):
        if self.thread.isRunning():
            self.worker.start()
        else:
            self.thread.start()

    def stop(self):
        if self.thread.isRunning():
            self.worker.stop()

    def closeEvent(self, event):
        self.worker.stop(abort=True)
        self.thread.quit()
        self.thread.wait()
  • Related