I am using pyvistaqt
and want display a progress bar window when I load data. I have success without using pyvista
with PyQt
(see this SO post), however it isn't working when I add vtk
.
I think something is still blocking the main thread, but I don't know what. Either the progress bar won't show at all, or if it does, half way through the bar stops loading and stops responding. Any help would be much appreciated:
Setup:
python 3.8.10
pyvista 0.32.1
qtpy 1.11.3
Output:
MRE
from pyvistaqt import MainWindow, QtInteractor
from qtpy import QtCore, QtGui, QtWidgets
class Worker(QtCore.QObject):
update = QtCore.Signal(int)
done = QtCore.Signal()
def __init__(self):
super().__init__()
def load(self):
for num in range(100):
for i in range(200000):
continue # Simulate long-running task
self.update.emit(num)
self.done.emit()
class Controller(object):
def __init__(self):
self.view = View(controller=self)
def on_load(self):
self.thread = QtCore.QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.view.show_progress_dialog()
self.thread.started.connect(lambda: self.worker.load())
self.worker.update.connect(self.view.progress_dialog.on_update)
def _on_finish():
self.view.hide_progress_dialog()
self.thread.quit()
self.worker.done.connect(_on_finish)
self.thread.finished.connect(self.worker.deleteLater)
self.thread.start()
class ProgressDialog(QtWidgets.QDialog):
def __init__(self, parent=None, title=None):
super().__init__(parent)
self.setWindowTitle(title)
self.pbar = QtWidgets.QProgressBar(self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.pbar)
self.setLayout(layout)
self.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)
self.resize(500, 50)
self.hide()
def on_update(self, value):
self.pbar.setValue(value)
class View(MainWindow):
def __init__(self, controller):
super().__init__()
self.controller = controller
self.container = QtWidgets.QFrame()
self.layout_ = QtWidgets.QGridLayout()
self.layout_.setContentsMargins(0, 0, 0, 0)
self.container.setLayout(self.layout_)
self.setCentralWidget(self.container)
self.progress_dialog = ProgressDialog(self)
self.btn = QtWidgets.QPushButton(self)
self.btn.setText("Load")
self.btn.clicked.connect(self.controller.on_load)
def show_progress_dialog(self):
self.progress_dialog.setModal(True)
self.progress_dialog.show()
def hide_progress_dialog(self):
self.progress_dialog.hide()
self.progress_dialog.setModal(False)
self.progress_dialog.pbar.reset()
self.progress_dialog.title = None
if __name__ == "__main__":
app = QtWidgets.QApplication([])
root = Controller()
root.view.show()
app.exec_()
"(Not Responding)" in Windows is usually the consequence of a deadlock. Historically, a lot of confusion has surfaced over whether to override run()
or to use moveToThread()
when dealing with QThread
:
Though both methods are accepted, I chose to use moveToThread()
because I learned it was best used when you have threads that need to interact with one another through signals and slots (see QThreads: Are You Using Them Wrong?)
After careful consideration, I removed the lambda
from self.thread.started.connect
and replaced it with
self.thread.started.connect(self.worker.load)
which fixed the problem. If arguments do need to be passed, an appropriate alternative would be to use functools.partial
.
I'm still not 100% sure why this fixed the problem, but I think it did because, in Python,:
lambda
s behave like closures and are evaluated at runtime (see How to Use Python Lambda Functions), andself
) doesn't get garbage collected (see this SO post)However, the above being the case, I'm not sure why the progress bar can run part way through (up to 51%) before hitting a deadlock...?
Alternatively, overriding QThread.run()
works, even though technically the thread affinity would be towards the main thread since moveToThread()
was not used, so I'm not sure how the signals and slots communicate without issue in this case.
Regardless of overriding QThread.run()
or using moveToThread()
, passing a lambda
to a slot
will cause this "(Not Responding)" error.
I would love it if someone could explain this!