Search code examples
pythonmultithreadingpyqtpyqt5qthread

Python - PyQt : Continue after a QThread is finished


I have a for loop in a QThread, which is started from the main GUI via a push button. When the for loop is over I would like to come back to the main thread (which is inside the Gui class) and do something else. As far as I understood, one should use the join method to wait for the thread to be finished. In my case it seems that MyThread is never finished.

import sys
from PyQt5 import QtCore
import PyQt5.QtWidgets as QtW
from PyQt5.QtCore import QThread

class MyWindow(QtW.QMainWindow):

  def __init__(self):
    super().__init__()
    self.setWindowTitle('MyWindow')
    self._main = QtW.QWidget()
    self.setCentralWidget(self._main) 
    self.button = QtW.QPushButton('Do it', self)
    self.button.clicked.connect(self.MyMethod)
    self.layout = QtW.QGridLayout(self)
    self.layout.addWidget(self.button)
    self.setLayout(self.layout)


  def MyMethod(self):
    self.n = 5
    self.loadthread = MyThread(self.n)
    self.loadthread.start()
    self.loadthread.join() # Will wait for the thread until it finishes its task
    print('thread finished')


class MyThread(QThread):

    def __init__(self, n):
        QThread.__init__(self)
        self.n = n

    def run(self):
        for i in range(self.n):
            print(i)
        print('cycle finished')


if __name__ == '__main__':
    app = QtCore.QCoreApplication.instance() # checks if QApplication already exists 
    if app is None: # create QApplication if it doesnt exist 
        app = QtW.QApplication(sys.argv)
    mainGui = MyWindow()
    mainGui.show()
    app.aboutToQuit.connect(app.deleteLater)
    app.exec_()

The output of the cose is

0
1
2
3
4
cycle finished

and print('thread finished') is never reached.


Solution

  • QThread does not have the join() method, so your application should quit unexpectedly and point to the following error message.

    QLayout: Attempting to add QLayout "" to MyWindow "", which already has a layout
    QWidget::setLayout: Attempting to set QLayout "" on MyWindow "", which already has a layout
    0
    1
    2
    3
    Traceback (most recent call last):
      File "main.py", line 24, in MyMethod
        self.loadthread.join() # Will wait for the thread until it finishes its task
    AttributeError: 'MyThread' object has no attribute 'join'
    4
    cycle finished
    Aborted (core dumped)
    

    If you want to execute some task after it is finished executing the thread you must use the finished signal of QThread:

    import sys
    from PyQt5 import QtCore, QtWidgets
    
    
    class MyWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle('MyWindow')
            self._main = QtWidgets.QWidget()
            self.setCentralWidget(self._main) 
            self.button = QtWidgets.QPushButton('Do it')
            self.button.clicked.connect(self.my_method)
            layout = QtWidgets.QGridLayout(self._main)
            layout.addWidget(self.button)
            layout.addWidget(self.button)
    
        @QtCore.pyqtSlot()
        def my_method(self):
            self.n = 5
            self.loadthread = MyThread(self.n, self)
            self.loadthread.finished.connect(self.on_finished)
            self.loadthread.start()
    
        @QtCore.pyqtSlot()
        def on_finished(self):
            print('thread finished')
    
    
    class MyThread(QtCore.QThread):
        def __init__(self, n, parent=None):
            QtCore.QThread.__init__(self, parent)
            self.n = n
    
        def run(self):
            for i in range(self.n):
                print(i)
            print('cycle finished')
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication.instance()
        if app is None: 
            app = QtWidgets.QApplication(sys.argv)
        mainGui = MyWindow()
        mainGui.show()
        app.aboutToQuit.connect(app.deleteLater)
        sys.exit(app.exec_())