如何使用QThread阻止GUI冻结?

时间:2015-03-02 16:25:20

标签: python multithreading pyqt4 python-multithreading qthread

我有一个GUI,需要执行需要一些时间的工作,我想展示这项工作的进度,类似于以下内容:

import sys
import time
from PyQt4 import QtGui, QtCore

class MyProgress(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        # start loop with signal
        self.button = QtGui.QPushButton('loop', self)
        self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)

        # test button
        self.test_button = QtGui.QPushButton('test')
        self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)

        self.pbar = QtGui.QProgressBar(self)
        self.pbar.setMinimum(0)
        self.pbar.setMaximum(100)

        # layout
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.test_button)
        vbox.addWidget(self.button)
        vbox.addWidget(self.pbar)
        self.setLayout(vbox)

        self.show()

    def update(self):
        self.pbar.setValue(self.pbar.value() + 1)

    def loop(self):
        for step in range(100):
            self.update()
            print step
            time.sleep(1)

    def test(self):
        if self.test_button.text() == 'test':
            self.test_button.setText('ok')
        else:
            self.test_button.setText('test')

app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop()  # call loop directly to check whether view is displayed
sys.exit(app.exec_())

当我执行代码时,调用loop方法并打印出值并更新进度条。但是view窗口小部件在执行loop期间将被阻止,虽然这对我的应用程序来说很好,但它对Ubuntu来说并不好看。所以我决定将工作转移到这样一个单独的线程:

import sys
import time
from PyQt4 import QtGui, QtCore

class Worker(QtCore.QObject):
    def __init__(self, parent=None):
        QtCore.QObject.__init__(self, parent)

    def loop(self):
        for step in range(10):
            print step
            time.sleep(1)

class MyProgress(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        # test button
        self.test_button = QtGui.QPushButton('test')
        self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)

        self.pbar = QtGui.QProgressBar(self)
        self.pbar.setMinimum(0)
        self.pbar.setMaximum(100)

        # layout
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(self.test_button)
        vbox.addWidget(self.pbar)
        self.setLayout(vbox)

        self.show()

    def test(self):
        if self.test_button.text() == 'test':
            self.test_button.setText('ok')
        else:
            self.test_button.setText('test')

app = QtGui.QApplication(sys.argv)

view = MyProgress()
work = Worker()

thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop)  # alternative
thread.start()
work.loop()  # not called if thread started() connected to loop

sys.exit(app.exec_())

当我运行此版本的脚本时,循环开始运行(步骤显示在终端中),但view小部件未显示。这是我不能完全遵循的第一件事。因为与此前版本的唯一区别在于循环在不同的对象中运行,但view小部件之前已创建,因此应该显示(就像前一个脚本的情况一样)。

然而,当我将信号started()thread连接到loop的{​​{1}}函数时,worker永远不会执行,尽管我启动了线程(在这种情况我没有在loop上拨打loop。另一方面,显示worker让我认为它取决于view是否被调用。但是,在app.exec_()上调用loop的脚本的第一个版本中,它显示了小部件,但它无法到达view

有谁知道这里发生了什么,可以解释如何执行app.exec_()(在一个单独的线程中)而不冻结loop

编辑:如果我添加view,应用程序会立即退出,而不会执行thread.finished.connect(app.exit)。我查看了this answer的第二个版本,这与我的基本相同。但在这两种情况下,它都能立即完成工作而不执行所需的方法,我无法真正发现原因。

1 个答案:

答案 0 :(得分:0)

这个例子不起作用,因为工作线程和GUI线程之间的通信都是单向的。

跨线程通信通常使用信号完成,因为这是一种简单的方法,可以确保一切都是以线程安全的方式异步完成的。 Qt通过将信号包装为事件来实现这一点,这意味着必须运行事件循环才能使一切正常工作。

要修复您的示例,请使用线程的started信号告诉工作人员开始工作,然后定期从工作人员发出自定义信号,告诉GUI更新进度条: / p>

class Worker(QtCore.QObject):
    valueChanged = QtCore.pyqtSignal(int)

    def loop(self):
        for step in range(0, 10):
            print step
            time.sleep(1)
            self.valueChanged.emit((step + 1) * 10)
...

thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()

sys.exit(app.exec_())