0

我正在使用pyvistaqt并希望在加载数据时显示进度条窗口。我在不使用pyvistawith 的情况下取得了成功PyQt(请参阅此 SO 帖子),但是当我添加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_()
4

1 回答 1

0

Windows 中的“(不响应)”通常是死锁的结果。从历史上看,在处理 时是否覆盖run()或使用出现了很多混淆:moveToThread()QThread

  1. 你这样做是错的...
  2. 你没有做错
  3. QThread 文档:不要阻止重新实现 QThread

虽然这两种方法都被接受,但我选择使用moveToThread()它是因为我知道当你有需要通过信号和插槽相互交互的线程时最好使用它(参见QThreads:你用错了吗?

经过仔细考虑,我删除了lambdafromself.thread.started.connect并替换为

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

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

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

  1. lambdas 的行为类似于闭包并在运行时进行评估(请参阅如何使用 Python Lambda 函数),以及
  2. 使用对自身 () 的引用向插槽创建信号self不会被垃圾收集(请参阅此 SO 帖子

但是,在上述情况下,我不确定为什么进度条在遇到死锁之前可以部分运行(高达 51%)......?

或者,重写QThread.run()工作,即使从技术上讲线程亲和性将朝向主线程,因为moveToThread()没有使用,所以我不确定在这种情况下信号和插槽如何通信而没有问题。

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

如果有人能解释这一点,我会很高兴的!

于 2021-12-16T03:54:04.843 回答