The majority of guides on QThread seem to focus on deleting the QThread after its QObject finishes. I would like to preserve QThread and QObject and reuse them when I need them again. This also means that I need to be more careful when managing their lifecycle because the Qt-python binding and their memory management can easily lead to some unexpected behaviour.
I am looking for the best practice to bind the lifecycle of the QThread and QObject to the GUI (QMainWindow, QFRame, etc...) which should perform proper clean-up during its destruction. I have prepared a minimal working example but maybe the community can point out its flaws and improve on it.
import sys
from PyQt6 import QtCore, QtWidgets
import time
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("MyWindow")
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) # this is OK
self.button = QtWidgets.QPushButton("Start thread")
self.setCentralWidget(self.button)
self.worker = MyObject()
self.thread = MyThread()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
# self.worker.finished.connect(self.worker.deleteLater) # for singleshot task
# self.thread.finished.connect(self.thread.deleteLater) # for singleshot task
# self.thread.start() # for singleshot task
self.button.clicked.connect(self.thread.start)
def __del__(self):
print("MyWindow __del__ entered")
self.thread.quit()
print("MyWindow __del__ quit issued, waiting on thread")
self.thread.wait()
print("MyWindow __del__ thread returned")
self.worker.deleteLater()
self.thread.deleteLater()
print("MyWindow __del__ quit")
# def closeEvent(self, qCloseEvent):
# qCloseEvent.accept() # Also leads to crashes
class MyObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
def run(self):
print("MyObject run entered")
time.sleep(1)
self.finished.emit()
print("MyObject run quit")
class MyThread(QtCore.QThread):
def __init__(self):
super().__init__()
def run(self):
print("MyThread run entered")
self.exec()
print("MyThread run quit")
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) # Causes crashes
app.exec()
This code produces the following result:
In [1]: runfile(...)
MyObject run entered
MyObject run quit
MyThread run entered
MyThread run quit
MyObject run entered
MyWindow __del__ entered
MyWindow __del__ quit issued, waiting on thread
MyWindow __del__ thread returned
MyWindow __del__ quit
MyObject run quit
MyThread run entered
In [2]: runfile(...)
MyThread run quit
This indicates that the thread was not deleted properly. Also, it surprised me that the __del__
function is not blocked on calling thread.wait()
, so the thread survives the QWidget. But at least it does not crash after multiple calls.