【问题标题】:PyQt Dialog not responsible while thread runningPyQt Dialog在线程运行时不负责
【发布时间】: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(),则对话框不会关闭。有什么方法可以确保一切都井井有条吗?

【问题讨论】:

    标签: python multithreading pyqt


    【解决方案1】:
    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)
      

    因此,按照不同线程的顺序更新 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 装饰插槽,则连接前无需移动。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-04
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2016-05-14
    • 2013-12-13
    • 1970-01-01
    相关资源
    最近更新 更多