线程运行时,PyQt Dialog不负责

时间:2016-02-16 10:46:46

标签: python multithreading pyqt

我想通过模态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 个答案:

答案 0 :(得分:2)

  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()
    

    修复很简单:在>附加信号之前移动工人

  2. 如果确保进度对话框在关闭之前收到要显示的命令,则无法进行竞争条件:

    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)
    
  3. 因此,按不同线程的顺序更新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_())