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_()
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_()