I have the following code:
import time
from PyQt5.QtCore import QThread, QObject
from PyQt5.QtWidgets import QWidget, QApplication
class Worker(QObject):
def run(self):
time.sleep(1)
print("Worker is finished")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.show()
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.on_thread_finished)
self.thread.start()
def on_thread_finished(self):
print("Thread finished")
self.thread.deleteLater()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
app.exec_()
Run it, it shows the window, prints Worker is finished, nothing more. That's weird. IMHO when the worker is finished, the thread should be finished too, which means the on_thread_finished method should be called and Thread finished should be printed. But it wasn't. Why?
when the worker is finished, the thread should be finished too
That's not how it works. Your Worker::run method is being invoked as a slot via the usual signal/slot mechanism after which the QThread will continue to process events as normal.
If you want to terminate the QThread when Worker::run has completed you need to tell it to do so explicitly...
import time
from PyQt5.QtCore import Qt, QThread, QObject
from PyQt5.QtWidgets import QWidget, QApplication
class Worker(QObject):
# Constructor accepts the QThread as a parameter and stashes it
# for later use.
def __init__(self, thread):
super(Worker, self).__init__()
self.m_thread = thread
def run(self):
time.sleep(1)
print("Worker is finished")
# We're done so ask the `QThread` to terminate.
if self.m_thread:
self.m_thread.quit()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.show()
self.thread = QThread()
# Pass the QThread to the Worker's ctor.
self.worker = Worker(self.thread)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.on_thread_finished)
self.thread.start()
def on_thread_finished(self):
print("Thread finished")
self.thread.deleteLater()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
app.exec_()
Very 'inelegant' but it conveys the idea.
A simpler alternative that springs to mind would be to simply make use of QThread::currentThread(), in which case your Worker::run method becomes...
class Worker(QObject):
def run(self):
time.sleep(1)
print("Worker is finished")
# We're done so ask the `QThread` to terminate.
QThread.currentThread().quit()
When you use moveToThread instead of reimplementing QThread.run, the thread will start its own event-loop which will wait until exit/quit is explicitly called. The finished signal is only emitted after the event-loop has stopped (and/or once run returns). The usual way to handle this scenario is to emit a custom signal from the worker, and connect it to the thread's quit slot. (NB: cross-thread signal-slot connections are guaranteed to be thread-safe).
So your example will work as expected with the following changes:
class Worker(QObject):
finished = QtCore.pyqtSignal()
def run(self):
time.sleep(1)
print("Worker is finished")
self.finished.emit()
class MainWindow(QWidget):
def __init__(self):
...
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With