PyQt5 - 即使使用 QThread,MainWindow 中的 QMovie 也无法播放

Posted

技术标签:

【中文标题】PyQt5 - 即使使用 QThread,MainWindow 中的 QMovie 也无法播放【英文标题】:PyQt5 - QMovie in MainWindow doesn't play even when using QThread 【发布时间】:2018-03-14 03:42:54 【问题描述】:

我试图在我的 PyQt5 QMainWindow 上显示一个加载 gif,同时一个密集的进程正在运行。 QMovie 不会正常播放,而是暂停。据我所知,事件循环不应该被阻塞,因为密集进程在它自己的 QObject 中传递给它自己的 QThread。相关代码如下:

QMainWindow:

class EclipseQa(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)
        self.initUI()

    def initUI(self):
        ...
        self.loadingMovie = QMovie("./loading.gif")
        self.loadingMovie.setScaledSize(QSize(149, 43))
        self.statusLbl = QLabel(self)
        self.statusLbl.setMovie(self.loadingMovie)
        self.grid.addWidget(self.statusLbl, 6, 2, 2, 2, alignment=Qt.AlignCenter)
        self.statusLbl.hide()
        ...

    def startLoadingGif(self):
        self.statusLbl.show()
        self.loadingMovie.start()

    def stopLoadingGif(self):
        self.loadingMovie.stop()
        self.statusLbl.hide()

    def maskDose(self):
        self.startLoadingGif()

        # Set up thread and associated worker object
        self.thread = QThread()
        self.worker = DcmReadWorker()
        self.worker.moveToThread(self.thread)
        self.worker.finished.connect(self.thread.quit)
        self.worker.updateRd.connect(self.updateRd)
        self.worker.updateRs.connect(self.updateRs)
        self.worker.updateStructures.connect(self.updateStructures)
        self.worker.clearRd.connect(self.clearRd)
        self.worker.clearRs.connect(self.clearRs)
        self.thread.started.connect(lambda: self.worker.dcmRead(caption, fname[0]))
        self.thread.finished.connect(self.stopLoadingGif)

        self.maskThread.start()

    def showDoneDialog(self):
        ...
        self.stopLoadingGif()
        ...

工人阶级:

class DoseMaskWorker(QObject):
    clearRd = pyqtSignal()
    clearRs = pyqtSignal()
    finished = pyqtSignal()
    startLoadingGif = pyqtSignal()
    stopLoadingGif = pyqtSignal()
    updateMaskedRd = pyqtSignal(str)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)

    @pyqtSlot(name="maskDose")
    def maskDose(self, rd, rdName, rdId, rs, maskingStructure_dict):
        ...
        self.updateMaskedRd.emit(maskedRdName)
        self.finished.emit()

为简洁起见,“...”表示我认为可能不相关的代码。

【问题讨论】:

【参考方案1】:

您在发出线程started 信号时使用lambda 调用插槽可能会导致它在主线程中执行。您需要做几件事来解决此问题。

首先,您对pyqtSlot 的使用不包含maskDose 方法的参数类型。您需要对其进行更新以使其正常运行。大概您还需要为您从 lambda 调用但未包含在您的代码中的 dcmRead 方法执行此操作。有关详细信息,请参阅documentation。

为了消除对 lambda 的使用,您需要在 EclipseQa 类中定义一个新信号和一个新槽。应该定义新信号,以便发出 dcmRead 方法所需数量的参数,并正确指定类型(相关文档也在上面的链接中)。这个信号应该连接到工作线程dcmRead 插槽(确保在工作线程对象被移动到线程之后执行此操作,否则您可能会遇到this 错误!)。插槽不应带参数,并连接到线程started 信号。插槽中的代码应该简单地发出带有适当参数的新信号以传递给dcmRead(例如self.my_new_signal.emit(param1, param2))。

注意:您可以通过从您希望检查的位置打印threading.current_thread().name,使用 Python 线程模块(即使使用 QThreads)检查任何代码正在哪个线程中运行。

注意 2:如果您的线程受 CPU 限制而不是 IO 限制,您可能仍然会遇到性能问题,因为 Python GIL 在任何时候只允许单个线程执行(尽管如此,它会定期在线程之间交换两个线程中的代码都应该运行,只是可能没有达到您期望的性能)。 QThreads(在 C++ 中实现并且理论上能够释放 GIL)对此没有帮助,因为它们正在运行您的 Python 代码,因此 GIL 仍然存在。

【讨论】:

完美!感谢您的大力帮助:)

以上是关于PyQt5 - 即使使用 QThread,MainWindow 中的 QMovie 也无法播放的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5中多线程模块QThread解决界面卡顿无响应问题,线程池ThreadPoolExecutor解决多任务耗时操作问题

PyQt5中多线程模块QThread解决界面卡顿无响应问题,线程池ThreadPoolExecutor解决多任务耗时操作问题

PyQt5 QThread 不会因终止或标志变量而停止

PyQt5 - 使用QThread更新Qtablewidget

PyQt5:使用 QObject 和 QThread 时出现 AttributeError

PyQt5 OpenCV 网络摄像头使用 QThread