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在线程运行时不负责的主要内容,如果未能解决你的问题,请参考以下文章

窗口关闭后 PyQt 线程仍在运行

从另一个 GUI 文件 PyQT5 打开 GUI 文件

PyQT5 多线程问题

WKWebView 在后台时不运行 JavaScript

带线程的 PyQt 进度条更新

PyQt5 QDialog 在后续线程中