为啥需要 processEvents() 才能让 QThread 工作?

Posted

技术标签:

【中文标题】为啥需要 processEvents() 才能让 QThread 工作?【英文标题】:Why is processEvents() needed to get QThread to work?为什么需要 processEvents() 才能让 QThread 工作? 【发布时间】:2018-02-15 07:31:44 【问题描述】:

下面是我列出目录的所有子目录的代码。我用它来理解 QThread 以及 PySide 中的信号和插槽。问题是,当我不在Main 类的scan() 方法中使用Qtcore.QApplication.processEvents() 时,代码不起作用。事件循环还没有运行吗?

import sys
import os
import time
from PySide import QtGui, QtCore

class Scanner(QtCore.QObject):
    folderFound = QtCore.Signal(str)
    done = QtCore.Signal()
    def __init__(self, path):
        super(Scanner, self).__init__()
        self.path = path

    @QtCore.Slot()
    def scan(self):
        for folder in os.listdir(self.path):
            time.sleep(1)
            self.folderFound.emit(folder)

class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.resize(420,130)
        self.setWindowTitle('Threads')
        self.lyt = QtGui.QVBoxLayout()
        self.setLayout(self.lyt)

        self.topLyt = QtGui.QHBoxLayout()
        self.lyt.addLayout(self.topLyt)
        self.scanBtn = QtGui.QPushButton('Scan')
        self.scanBtn.clicked.connect(self.scan)
        self.clearBtn = QtGui.QPushButton('Clear')
        self.topLyt.addWidget(self.scanBtn)
        self.topLyt.addWidget(self.clearBtn)

        self.folders = list()

        self.show()

    def scan(self):
        self.th = QtCore.QThread()
        scanner = Scanner(r"D:\\")
        scanner.moveToThread(self.th)

        scanner.folderFound.connect(self.addFolder)
        scanner.done.connect(scanner.deleteLater)
        scanner.done.connect(self.quit)

        self.th.started.connect(scanner.scan)
        self.th.start()

        QtCore.QApplication.processEvents()

    @QtCore.Slot()
    def addFolder(self, folder):
        lbl = QtGui.QLabel(folder)
        self.folders.append(lbl)

        self.lyt.addWidget(lbl)

    @QtCore.Slot()
    def quit(self):
        self.th.quit()
        self.th.wait()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    main = Main()    
    app.exec_()

【问题讨论】:

“不工作”是什么意思? 这意味着当我单击“扫描”按钮时,没有任何反应,但是当我使用 processEvents 时,它开始在从线程获取的标签中添加文件夹名称。 qt-project.org/wiki/ThreadsEventsQObjects Updating GUI elements in MultiThreaded PyQT的可能重复 【参考方案1】:

您的示例完全有效,这纯属侥幸。

当您尝试调用QtCore.QApplication.processEvents() 时,将引发NameError,因为QApplication 类实际上在QtGui 模块中,而不是QtCore 模块中。但是,引发异常的副作用是阻止您的 scanner 对象被垃圾收集,因此线程似乎正常运行。

修复代码的正确方法是保留对scanner 对象的引用,并去掉不需要的processEvents 行:

def scan(self):
    self.th = QtCore.QThread()
    # keep a reference to the scanner
    self.scanner = Scanner(r"D:\\")
    self.scanner.moveToThread(self.th)

    self.scanner.folderFound.connect(self.addFolder)
    self.scanner.done.connect(self.scanner.deleteLater)
    self.scanner.done.connect(self.quit)

    self.th.started.connect(self.scanner.scan)
    self.th.start()

您的代码的其余部分没问题,该示例现在将按预期工作。

【讨论】:

【参考方案2】:

事件循环不是在你背后运行的东西。您始终是必须运行它的人。一个线程不能同时做两件事:如果您的代码是控制点,那么显然事件循环不是!您需要从代码返回到事件循环,并确保您的代码是从事件循环中调用的。任何类型的 UI 信号、网络事件或超时都是从事件循环中调用的,因此很可能您的代码已经在调用堆栈上具有事件循环。但是,要保持循环旋转,您必须返回它。

永远不要使用processEvents - 相反,反转控件,以便事件循环调用您的代码,然后您执行大量工作,最后返回到事件循环。

“让我的代码在事件循环中工作”的习语是零持续时间计时器。执行工作的可调用对象附加到超时信号。

【讨论】:

以上是关于为啥需要 processEvents() 才能让 QThread 工作?的主要内容,如果未能解决你的问题,请参考以下文章

Qt QApplication::processEvents();//不停地处理事件,让程序保持响应

为啥会这样:处理需要通过才能继续? [关闭]

为啥 Apache + PHP 需要执行权限才能写入文件?

QCoreApplication.processEvents 行为

为啥我必须注释掉我的 NSPredicate 才能让 NSFetchedResultsController 填充 UITableView?

为啥这个 pg 查询这么慢?我怎样才能让它更快?