PyQt Dialog在线程运行时不负责
Posted
技术标签:
【中文标题】PyQt Dialog在线程运行时不负责【英文标题】:PyQt Dialog not responsible while thread running 【发布时间】:2016-02-16 10:46:46 【问题描述】:我想通过模态QDialog
显示加载进度。所以我创建了一个线程来加载数据并在对话框中调用exec()
。
loading_progress_dialog = LoadingProgressDialog(len(filenames))
loadingWorker = analyzer.LoadingWorker(filenames, loading_progress_dialog.apply_progress)
workingThread = QThread()
workingThread.started.connect(loadingWorker.process)
loadingWorker.finished.connect(workingThread.quit)
workingThread.finished.connect(loading_progress_dialog.accept)
loadingWorker.moveToThread(workingThread)
workingThread.start()
loading_progress_dialog.exec()
我希望对话框负责,但它会冻结,并且在加载线程运行时我无法在屏幕上移动它。
class LoadingProgressDialog(QLoadingProgressDialog, Ui_LoadingDialog):
def __init__(self, maxFiles):
super(LoadingProgressDialog, self).__init__()
self.setupUi(self)
self.progressBar.setMaximum(maxFiles)
self.setWindowTitle('Loading files...')
def apply_progress(self, delta_progress):
self.progressBar.setValue(delta_progress + self.progressBar.value())
class LoadingWorker(QtCore.QObject):
def __init__(self, file_names, progress_made):
super(LoadingWorker, self).__init__()
self._file_names = file_names
self._progress_made = progress_made
finished = QtCore.pyqtSignal()
def process(self):
print("Thread started")
# load_csv_data(self._file_names, self._progress_made)
QtCore.QThread.sleep(5)
self.finished.emit()
我是在与 GIL 打架还是另一个问题?我担心的第二件事是self.finished.emit()
和loading_progress_dialog.exec()
之间的竞争条件。如果工作线程完成的速度快于 gui 线程运行exec()
,则对话框不会关闭。有什么方法可以确保一切都井井有条吗?
【问题讨论】:
【参考方案1】:您的 GUI 冻结,因为它与您的工作线程在同一线程中执行 - 在主线程中!如果您将工作人员移动到不同的线程,这怎么可能?好吧,让我们来看看你到底做了什么:
# This connects signal to the instance of worker located in main thread
workingThread.started.connect(loadingWorker.process)
# Creates a copy of worker in the different thread
loadingWorker.moveToThread(workingThread)
# Signal reaches the instance of worker it was connected to -
# the instance belonging to main thread!
workingThread.start()
解决方法很简单:在向其附加信号之前移动工人。
如果保证进度对话框在关闭之前接收到要显示的命令,则竞争条件是不可能的:
class LoadingWorker(QtCore.QObject):
[...]
def process(self):
self.ready.emit()
[...]
self.finished.emit()
loadingWorker.ready.connect(loading_progress_dialog.exec)
loadingWorker.finished.connect(loading_progress_dialog.close)
因此,按照不同线程的顺序更新 UI 的简单程序可能如下所示:
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QThread
from time import sleep
class LoadingProgressDialog(QtGui.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Loading files...')
def show_progress(self, p):
self.setWindowTitle('Loading files... %'.format(p))
class LoadingWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
ready = QtCore.pyqtSignal()
report_progress = QtCore.pyqtSignal(object)
def process(self):
print('Worker thread ID: %s' % int(QThread.currentThreadId()))
print("Worker started")
self.ready.emit()
for p in range(0, 100, 10):
self.report_progress.emit(p)
sleep(0.2)
print("Worker terminates...")
self.finished.emit()
if __name__ == '__main__':
import sys
app = QtGui.QApplication([])
print('Main thread ID: %s' % int(QThread.currentThreadId()))
workingThread = QThread()
loadingWorker = LoadingWorker()
loading_progress_dialog = LoadingProgressDialog()
loadingWorker.ready.connect(loading_progress_dialog.exec)
loadingWorker.report_progress.connect(loading_progress_dialog.show_progress)
loadingWorker.finished.connect(workingThread.quit)
loadingWorker.finished.connect(loading_progress_dialog.close)
loadingWorker.moveToThread(workingThread)
workingThread.started.connect(loadingWorker.process)
workingThread.start()
sys.exit(app.exec_())
【讨论】:
关于为什么在连接信号之前需要移动到线程的讨论可以找到here。如果您使用@pyqtSlot
装饰插槽,则连接前无需移动。以上是关于PyQt Dialog在线程运行时不负责的主要内容,如果未能解决你的问题,请参考以下文章