为什么这个Python/Qt代码的执行是异步的

2024-10-02 12:24:02 发布

您现在位置:Python中文网/ 问答频道 /正文

今天我遇到了一个有趣的情况,当下面的一行在执行它之前被执行(上面的一行)。下面是这种情况的简单版本。简要说明:有三个QGroupBox()连接到同一个窗口:groupboxAgroupboxBgroupboxC。 Groupbox B已隐藏。单击“确定”按钮可取消隐藏第二个groupBox B并隐藏第一个groupBox a。重复单击可继续此隐藏/取消隐藏事件序列。你知道吗

最后一行是调用calcA()方法,它的处理大约需要2-3秒才能完成。 一个问题是:即使对calcA()的调用在最后一行,它也是在QGroupBoxes可见性发生变化之前执行的,而上面编写的代码负责这些变化。我想知道为什么会发生这种情况,以及如何避免这种情况。你知道吗

enter image description here

from PyQt4 import QtGui, QtCore

class MyApp(object):

    def __init__(self):
        super(MyApp, self).__init__()

        app = QtGui.QApplication(sys.argv)
        self.mainWidget = QtGui.QWidget()
        self.mainLayout = QtGui.QVBoxLayout()
        self.mainWidget.setLayout(self.mainLayout)

        # A
        self.groupboxA = QtGui.QGroupBox()
        self.layoutA = QtGui.QVBoxLayout()
        self.groupboxA.setLayout(self.layoutA)

        lineA = QtGui.QTextEdit('This is QGroupBox A')
        self.layoutA.addWidget(lineA) 

        # B
        self.groupboxB = QtGui.QGroupBox()
        self.layoutB = QtGui.QVBoxLayout()
        self.groupboxB.setLayout(self.layoutB)
        self.groupboxB.setHidden(True)

        labelB = QtGui.QLabel('This is QGroupBox B')
        self.layoutB.addWidget(labelB) 

        # C
        self.groupboxC = QtGui.QGroupBox()
        self.layoutC = QtGui.QVBoxLayout()
        self.groupboxC.setLayout(self.layoutC)

        okButton = QtGui.QPushButton('OK')
        okButton.clicked.connect(self.OK)      
        self.layoutC.addWidget(okButton) 


        self.mainLayout.addWidget(self.groupboxA)
        self.mainLayout.addWidget(self.groupboxB)
        self.mainLayout.addWidget(self.groupboxC)
        self.mainWidget.show()
        sys.exit(app.exec_())

    def calcA(arg=None):
        print "Task started..."
        for i in range(5000000):
            pass
        print '...task completed.'


    def OK(self):
        if self.groupboxA.isHidden(): self.groupboxA.setHidden(False)
        else: self.groupboxA.setHidden(True)
        if self.groupboxB.isHidden(): self.groupboxB.setHidden(False)
        else: self.groupboxB.setHidden(True)
        self.calcA()

if __name__ == '__main__':
    MyApp()

Tags: selfdef情况myappqtguiqvboxlayoutaddwidgetmainlayout
2条回答

直到控件返回到Qt事件循环(一旦slotOK完成),GUI的更新才会发生。你知道吗

这是一个常见的问题,当您有一个长期运行的插槽,并希望图形用户界面的变化是可见的插槽结束之前。这个问题有一些答案,其中详细说明了一些解决方法: Label in PyQt4 GUI not updating with every loop of FOR loop

基本上,将长时间运行的工作卸载到QThread通常是最好的主意,但是只有在长时间运行的代码不直接与GUI交互的情况下,您才能轻松地做到这一点(您无法从线程与GUI交互,因此这会变得很棘手)。线程很容易出错,并且会严重破坏程序的稳定性,因此您可能不想走这条路。你知道吗

另一个选项是在__init__方法中调用QApplication.instance().processEvents()(或将app分配给self.app),然后在更新GUI的调用之后立即调用self.app.processEvents()(如setHidden()),但我通常会尽量避免这种情况,因为我认为这是一种不好的做法。你知道吗

最后的解决方案,也是在您的情况下最有效的解决方案,是调用QTimer.singleShot(1,self.calcA),而不是self.calcA。这将把控制权返回到事件循环,事件循环将更新GUI,然后调用self.calcA。你知道吗

问题是你有这样的伪代码:

def OK(self):
    modify GUI
    self.long_running_function()

微妙的是,对GUI所做的修改只有在Qt事件循环恢复之后,即在OK()返回之后才会得到“处理”。这就是为什么看起来好像长时间运行的函数是在上面的行之前执行的。不是这样,但是上面的行只有在OK()返回时才得到“activate”,这是在长时间运行的函数完成之后。你知道吗

因此,如果您想让GUI更新在可见之前完成,就不能在OK中长时间运行函数。解决方案取决于长时间运行的函数:如果它是一个循环,您可以这样编写:

def long_running_function(self):
    while condition: 
       self.doQuickStuff()

然后可以替换为:

def doQuickStuff(self):
    if condition:
        .... do stuff ....
        # repeat:
        # 10 ms should give time for some event processing
        QTimer.singleShot(10, self.doQuickStuff) 

def OK(self):
    modify GUI
    doQuickStuff()

否则,只需将长时间运行的函数移到QThread。您必须考虑以下问题:

  1. 需要向用户更新正在进行的计算/操作,稍后将提供结果
  2. 可能需要定期更新用户的实际进度
  3. 您需要一种方法来中断计算,以防用户在线程仍在运行时退出应用程序

相关问题 更多 >

    热门问题