PyQt5 - 即使使用 QThread,MainWindow 中的 QMovie 也无法播放
PyQt5 - QMovie in MainWindow doesn't play even when using QThread
我正在尝试在我的 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:
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()
为简洁起见,“...”表示我认为可能不相关的代码。
当线程 started
发出信号时,您使用 lambda
调用插槽很可能导致它在主线程中执行。您需要做几件事来解决这个问题。
首先,您对 pyqtSlot
的使用不包含 maskDose
方法的参数类型。您需要更新它才能做到。大概您还需要为从 lambda
调用但未包含在您的代码中的 dcmRead
方法执行此操作。有关详细信息,请参阅 documentation。
为了删除 lambda 的使用,您需要在 EclipseQa
class 中定义一个新信号和一个新槽。应定义新信号,以便发出 dcmRead
方法所需的参数数量,并正确指定类型(相关文档也在上面的 link 中)。这个信号应该连接到 worker dcmRead
槽(确保在 worker 对象被移动到线程之后再做它,否则你可能 运行 进入 this 错误!)。该插槽不应带任何参数,并连接到线程 started
信号。插槽中的代码应该简单地发出带有要传递给 dcmRead
的适当参数的新信号(例如 self.my_new_signal.emit(param1, param2)
)。
注意:在使用 Python 线程模块(即使使用 QThreads 时),您可以通过从您希望的位置打印 threading.current_thread().name
来检查任何代码 运行ning 是哪个线程检查。
注意 2:如果你的线程是 CPU 绑定而不是 IO 绑定,你可能仍然会遇到性能问题,因为 Python GIL 只允许一个线程在任何时候执行(它会定期在线程之间交换,所以两个线程中的代码应该 运行,只是可能达不到您期望的性能)。 QThreads(用 C++ 实现并且理论上能够释放 GIL)对此没有帮助,因为它们正在 运行ning 您的 Python 代码,因此 GIL 仍然保留。
我正在尝试在我的 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:
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()
为简洁起见,“...”表示我认为可能不相关的代码。
当线程 started
发出信号时,您使用 lambda
调用插槽很可能导致它在主线程中执行。您需要做几件事来解决这个问题。
首先,您对 pyqtSlot
的使用不包含 maskDose
方法的参数类型。您需要更新它才能做到。大概您还需要为从 lambda
调用但未包含在您的代码中的 dcmRead
方法执行此操作。有关详细信息,请参阅 documentation。
为了删除 lambda 的使用,您需要在 EclipseQa
class 中定义一个新信号和一个新槽。应定义新信号,以便发出 dcmRead
方法所需的参数数量,并正确指定类型(相关文档也在上面的 link 中)。这个信号应该连接到 worker dcmRead
槽(确保在 worker 对象被移动到线程之后再做它,否则你可能 运行 进入 this 错误!)。该插槽不应带任何参数,并连接到线程 started
信号。插槽中的代码应该简单地发出带有要传递给 dcmRead
的适当参数的新信号(例如 self.my_new_signal.emit(param1, param2)
)。
注意:在使用 Python 线程模块(即使使用 QThreads 时),您可以通过从您希望的位置打印 threading.current_thread().name
来检查任何代码 运行ning 是哪个线程检查。
注意 2:如果你的线程是 CPU 绑定而不是 IO 绑定,你可能仍然会遇到性能问题,因为 Python GIL 只允许一个线程在任何时候执行(它会定期在线程之间交换,所以两个线程中的代码应该 运行,只是可能达不到您期望的性能)。 QThreads(用 C++ 实现并且理论上能够释放 GIL)对此没有帮助,因为它们正在 运行ning 您的 Python 代码,因此 GIL 仍然保留。