`QProgressDialog` 没有响应

`QProgressDialog` Not Responding

我正在使用 pyvistaqt 并希望在加载数据时显示进度条 window。我没有将 pyvistaPyQt 一起使用就成功了(请参阅 ),但是当我添加 vtk.

时它不起作用

我认为某些东西仍在阻塞主线程,但我不知道是什么。进度条要么根本不显示,要么即使显示,进度条中途也会停止加载并停止响应。任何帮助将不胜感激:

设置:

python 3.8.10
pyvista 0.32.1
qtpy 1.11.3

输出:

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 通常是死锁的结果。从历史上看,在处理 QThread:

时,是否覆盖 run() 或使用 moveToThread() 存在很多混淆
  1. You're doing it wrong...
  2. You were not doing so wrong
  3. QThread documentation: do not discourage the reimplementation of QThread

虽然这两种方法都被接受,但我选择使用 moveToThread(),因为我了解到当线程需要通过信号和槽相互交互时最好使用它(参见 QThreads: Are You Using Them Wrong?

经过深思熟虑,我把self.thread.started.connect中的lambda去掉,换成了

self.thread.started.connect(self.worker.load)

这解决了问题。如果确实需要传递参数,一个合适的替代方法是使用 functools.partial.

我仍然不是 100% 确定为什么这解决了问题,但我认为它确实是因为,在 Python,:

  1. lambdas 的行为类似于闭包,并在 运行 时间进行评估(参见 How to Use Python Lambda Functions),并且
  2. 通过引用自身 (self) 向插槽创建信号不会被垃圾收集(参见

但是,如果是上述情况,我不确定为什么进度条可以 运行 在遇到死锁之前中途(最多 51%)...?

或者,重写 QThread.run() 是可行的,尽管从技术上讲线程亲和力是针对主线程的,因为 moveToThread() 没有被使用,所以我不确定信号和槽如何在不使用的情况下进行通信这种情况下的问题。

无论覆盖 QThread.run() 还是使用 moveToThread(),将 lambda 传递给 slot 都会导致此“(未响应)”错误。

如果有人能解释一下,我会很高兴!