PySide / PyQt的 - 启动CPU密集型线程挂起整个应用程序(PySide/PyQt -

2019-06-25 21:35发布

我试图做一个相当普遍的事情在我的PySide GUI应用程序:我要委托一些CPU密集型任务的后台线程,使我的GUI保持敏感,并为云计算甚至可以显示一个进度指示器。

下面是我在做什么(我使用PySide 1.1.1的Python 2.7,Linux的x86_64的):

import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot

class Worker(QObject):
    done_signal = Signal()

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

    @Slot()
    def do_stuff(self):
        print "[thread %x] computation started" % self.thread().currentThreadId()
        for i in range(30):
            # time.sleep(0.2)
            x = 1000000
            y = 100**x
        print "[thread %x] computation ended" % self.thread().currentThreadId()
        self.done_signal.emit()


class Example(QWidget):

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

        self.initUI()

        self.work_thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.work_thread)
        self.work_thread.started.connect(self.worker.do_stuff)
        self.worker.done_signal.connect(self.work_done)

    def initUI(self):

        self.btn = QPushButton('Do stuff', self)
        self.btn.resize(self.btn.sizeHint())
        self.btn.move(50, 50)       
        self.btn.clicked.connect(self.execute_thread)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Test')    
        self.show()


    def execute_thread(self):
        self.btn.setEnabled(False)
        self.btn.setText('Waiting...')
        self.work_thread.start()
        print "[main %x] started" % (self.thread().currentThreadId())

    def work_done(self):
        self.btn.setText('Do stuff')
        self.btn.setEnabled(True)
        self.work_thread.exit()
        print "[main %x] ended" % (self.thread().currentThreadId())


def main():

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

该应用程序显示的一个按钮的单个窗口。 当按下按钮,我希望在执行计算它禁用本身。 那么,应该重新启用按钮。

会发生什么,取而代之的,是当我按下按钮整个窗口冻结而计算云,然后,它结束时,我重新获得应用程序的控制。 按钮似乎从来没有被禁用。 我注意到一个有趣的事情是,如果我替换CPU密集型计算do_stuff()用一个简单的time.sleep()程序行为与预期相同。

我不知道到底发生了什么事情,但现在看来,第二个线程的优先级是如此之高,它实际上是防止以往任何时候都被调度的GUI线程。 如果第二个线程进入受阻状态(因为它有一个发生sleep()图形用户界面实际上已经运行的机会,如预期更新的接口。 我试图改变工作线程的优先级,但它看起来像它不能在Linux上完成。

另外,我尝试打印线程ID,但我不知道如果我做正确。 如果我的线程关联性似乎是正确的。

我也试过用程序和PyQt的行为是完全一样的,因此,标签和标题。 如果我可以把它与PyQt4的,而不是PySide来看,我可以转我的整个应用程序PyQt4的

Answer 1:

这可能是由于工作线程持有Python的GIL造成的。 在一些Python实现,只有一个Python的线程可以同时执行。 吉尔阻止执行Python代码其他线程,并且在不需要的GIL函数调用被释放。

例如,GIL被实际IO过程中释放,因为IO由操作系统而不是Python解释处理。

解决方案:

  1. 显然,你可以使用time.sleep(0)在你的工作线程(以提供给其他线程根据该SO问题 )。 你必须定期调用time.sleep(0)自己,而后台线程调用这个函数GUI线程才能运行。

  2. 如果工作线程是充分自足的,你可以把它变成一个完全独立的进程,然后通过在管道发送腌制对象进行通信。 在前台进程,创建一个工作线程做IO与后台进程。 由于工作线程会做IO而不是CPU的操作,也不会持有GIL,这将带给你完全响应GUI线程。

  3. 一些Python实现(JPython和IronPython的)没有GIL。

在CPython的线程是仅适用于复用IO操作非常有用,而不是把CPU密集型任务的背景。 对于许多应用,在CPython的执行线程是从根本上打破,它很可能会持续这种可预见的未来。



Answer 2:

在结束这个工作对我的问题 - 所以可以将代码帮助别人。

import sys
from PySide import QtCore, QtGui
import time

class qOB(QtCore.QObject):

    send_data = QtCore.Signal(float, float)

    def __init__(self, parent = None):
        QtCore.QObject.__init__(self)
        self.parent = None
        self._emit_locked = 1
        self._emit_mutex = QtCore.QMutex()

    def get_emit_locked(self):
        self._emit_mutex.lock()
        value = self._emit_locked
        self._emit_mutex.unlock()
        return value

    @QtCore.Slot(int)
    def set_emit_locked(self, value):
        self._emit_mutex.lock()
        self._emit_locked = value
        self._emit_mutex.unlock()

    @QtCore.Slot()
    def execute(self):
        t2_z = 0
        t1_z  = 0
        while True:
            t = time.clock()

            if self.get_emit_locked() == 1: # cleaner
            #if self._emit_locked == 1: # seems a bit faster but less               responsive, t1 = 0.07, t2 = 150
                self.set_emit_locked(0)
                self.send_data.emit((t-t1_z)*1000, (t-t2_z)*1000)
                t2_z = t

            t1_z = t

class window(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        self.l = QtGui.QLabel(self)
        self.l.setText("eins")

        self.l2 = QtGui.QLabel(self)
        self.l2.setText("zwei")

        self.l2.move(0, 20) 

        self.show()

        self.q = qOB(self)
        self.q.send_data.connect(self.setLabel)

        self.t = QtCore.QThread()
        self.t.started.connect(self.q.execute)
        self.q.moveToThread(self.t)

        self.t.start()

    @QtCore.Slot(float, float)
    def setLabel(self, inp1, inp2):

        self.l.setText(str(inp1))
        self.l2.setText(str(inp2))

        self.q.set_emit_locked(1)



if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    win = window()
    sys.exit(app.exec_())


文章来源: PySide/PyQt - Starting a CPU intensive thread hangs the whole application