Search code examples
pythonmultithreadingpyqtpyqt5qthread

Python - PyQt: restarting a thread which is finished


I have pyqt interface which starts a QThread if a push button is pressed. The thread run and when it's finished I can start it again, no problems up to here. I now added a check box for continuous operation of the thread. If checkbox is checked the pushbutton remains pressed and one has to push again the button to stop the thread.

Apparently after stopping, the thread is properly finished (checked also with isRunning() method) but if I try to start it again in continuous operation the program will crash. This doesn't happen if I instead restart it with continuous operation off, but the thread is started and stopped twice in this case.

How to explain such behavior? Is there a way to make it work as intended?

An example:

import sys
from PyQt5 import QtCore
import PyQt5.QtWidgets as QtW
from PyQt5.QtCore import QThread, pyqtSlot, pyqtSignal
import time

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.do)
    self.contincheck = QtW.QCheckBox("Continuous")
    self.contincheck.clicked.connect(self.continuous_doing)
    self.continuous = False
    self.layout = QtW.QGridLayout(self._main)
    self.layout.addWidget(self.button,0,0)
    self.layout.addWidget(self.contincheck,1,0)
    self.setLayout(self.layout)
    self.show()

  def continuous_doing(self):
    if self.contincheck.isChecked():
      self.button.setCheckable(True)
      self.continuous = True
    else:
      self.button.setCheckable(False)
      self.continuous = False

  def do(self):
    if self.button.isCheckable() and not self.button.isChecked():
        self.button.setText('Do it')
        self.button.clicked.connect(self.do)
        self.contincheck.setEnabled(True)
    else:
        self.mythread = MyThread(self.continuous)
        if self.button.isCheckable() and self.button.isChecked():
            self.button.setText('Stop doing that')
            self.contincheck.setDisabled(True)
            self.button.clicked.connect(self.mythread.stop)
        self.mythread.finished.connect(self.thread_finished)
        self.mythread.signal.connect(self.done)
        self.mythread.start()

  @pyqtSlot(int)
  def done(self, i):
      print('done it', i)

  @pyqtSlot()
  def thread_finished(self):
      print('thread finished')



class MyThread(QThread):
    signal = pyqtSignal(int)

    def __init__(self, continuous):
        QThread.__init__(self)
        self._stopped = True
        self.continuous = continuous
        self.i = 0

    def __del__(self):
        self.wait()

    def stop(self):
        self._stopped = True

    def run(self):
        self._stopped = False
        while True:
            self.signal.emit(self.i)
            if self._stopped:
                break
            if self.continuous: time.sleep(2)
            else: break


if __name__ == '__main__':
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtW.QApplication(sys.argv)
    mainGui = MyWindow()
    mainGui.show()
    app.aboutToQuit.connect(app.deleteLater)
    app.exec_()

Solution

  • The problem is that you are creating a new thread instead of reusing the existing one, in the following example I show you how to do it correctly:

    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.do)
    
            self.contincheck = QtWidgets.QCheckBox("Continuous")
            self.contincheck.clicked.connect(self.continuous_doing)
            self.continuous = False
            layout = QtWidgets.QGridLayout(self._main)
            layout.addWidget(self.button, 0, 0)
            layout.addWidget(self.contincheck, 1, 0)
    
            self.mythread = MyThread(self.continuous, self)
            self.mythread.finished.connect(self.thread_finished)
            self.button.clicked.connect(self.mythread.stop)
            self.mythread.signal.connect(self.done)
    
        def continuous_doing(self):
            self.button.setCheckable(self.contincheck.isChecked())
            self.continuous = self.contincheck.isChecked()
    
        def do(self):
            if self.button.isCheckable() and not self.button.isChecked():
                self.button.setText('Do it')
                self.contincheck.setEnabled(True)
            else:
                self.mythread.continuous = self.continuous
                if self.button.isCheckable() and self.button.isChecked():
                    self.button.setText('Stop doing that')
                    self.contincheck.setDisabled(True)
    
                self.mythread.start()
    
        @QtCore.pyqtSlot(int)
        def done(self, i):
            print('done it', i)
    
        @QtCore.pyqtSlot()
        def thread_finished(self):
            print('thread finished')
    
    
    class MyThread(QtCore.QThread):
        signal = QtCore.pyqtSignal(int)
    
        def __init__(self, continuous=False, parent=None):
            super(MyThread, self).__init__(parent)
            self._stopped = True
            self.continuous = continuous
            self.i = 0
    
        def __del__(self):
            self.wait()
    
        def stop(self):
            self._stopped = True
    
        def run(self):
            self._stopped = False
            while True:
                self.signal.emit(self.i)
                if self._stopped:
                    break
                if self.continuous: 
                    QtCore.QThread.sleep(2)
                else: 
                    break
    
    
    if __name__ == '__main__':
        app = QtCore.QCoreApplication.instance()
        if app is None:
            app = QtWidgets.QApplication(sys.argv)
        mainGui = MyWindow()
        mainGui.show()
        app.aboutToQuit.connect(app.deleteLater)
        app.exec_()