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

时间:2014-05-03 06:20:02

标签: python qt pyqt

今天我遇到了一个有趣的情况,下面的一行在行前行(上面一行)之前执行。这种情况的简单版本发布在下面。简要说明:有三个QGroupBox()连接到同一窗口:groupboxAgroupboxBgroupboxC。 Groupbox B被隐藏。单击“确定”按钮将取消隐藏第二个组框B并隐藏第一个组框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()

2 个答案:

答案 0 :(得分:2)

在将控件返回到Qt事件循环(一旦您的插槽OK完成)之后,不会更新GUI。

当您有一个长时间运行的插槽并希望在插槽结束之前可以看到GUI更改时,这是一个常见问题。这个问题有一些答案,详细介绍了一些解决方法: Label in PyQt4 GUI not updating with every loop of FOR loop

基本上,将长时间运行的工作卸载到QThread通常是最好的想法,但是如果长时间运行的代码不直接与GUI交互(您无法通过线程与GUI交互),则只能轻松地执行此操作,所以它变得棘手)。线程很容易出错,并且会严重破坏程序的稳定性,所以你可能不想走这条路。

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

最终解决方案以及可能在您的情况下效果最佳的解决方案是拨打QTimer.singleShot(1,self.calcA)而不是self.calcA。这会将控制权返回给事件循环,这将更新您的GUI,然后调用self.calcA

答案 1 :(得分:1)

问题是你有这个,伪代码:

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

微妙之处在于,只有在Qt事件循环恢复后,即在OK()返回后,对GUI的修改才会被“处理”。这就是为什么看起来你的长时间运行功能在它上面的行之前执行。它不是,但是一旦OK()返回,它上面的行只会“激活”,这是在你的长时间运行功能完成之后。

因此,如果您希望之前进行的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. 如果用户在线程仍在运行时退出应用程序,则需要一种中断计算的方法