Qthread 不工作,GUI 仍然挂起

Posted

技术标签:

【中文标题】Qthread 不工作,GUI 仍然挂起【英文标题】:Qthread not working and GUI still hangs up 【发布时间】:2014-05-25 10:28:23 【问题描述】:

我正在尝试实现一个使用 pyqt 进行线程化的基本示例,其中有一个文本框在处理一些代码时会定期更新。我试图删除任何不必要的依赖并尽可能地抽象代码,因此我只是将要在后台处理的代码替换为 while 循环,该循环在每个循环中发送数据以供文本框读取。但是,它不起作用,我不知道是什么导致 UI 挂断。我为当前线程 id 添加了调试语句,主类和工作类(在我的示例中称为 Process)都匹配

#!/zin/tools/bin/python
#vim:expandtab filetype=python nocindent sw=4

import sys, traceback
import os
import subprocess
import time 
from PyQt4 import QtCore,QtGui


try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Process (QtCore.QObject):
    processCmdDone = QtCore.pyqtSignal()
    processdataReady = QtCore.pyqtSignal(str)

    def __init__(self):
        super(Process, self).__init__()
        self.processdataReady.connect(self.debug)

    def debug(self):
        print  "signal process data ready is invoked from within the process"

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(str)
    def execCmd(self):
        print 'worker thread id :'+ str (QtCore.QThread.currentThreadId())
        x = 0
        while x < 100:
            x+=1
            self.processdataReady.emit(str(x)+'\n')
            time.sleep(1)
        self.processCmdDone.emit()
        print "process ended"


class MainWindow (QtGui.QWidget):
    def __init__(self,filename = None, parent=None):
        super(MainWindow,self).__init__(parent)
        self.transcript_textEdit = QtGui.QTextBrowser()
        font = QtGui.QFont()
            font.setPointSize(12)
            self.transcript_textEdit.setFont(font)
            self.transcript_textEdit.setObjectName(_fromUtf8("transcript_textEdit"))
            self.layout = QtGui.QVBoxLayout(self)
            self.layout.addWidget(self.transcript_textEdit)

        self.run_pushButton = QtGui.QPushButton()
            self.run_pushButton.setFont(font)
            self.run_pushButton.setObjectName(_fromUtf8("run_pushButton"))
            self.run_pushButton.setText('Run')
            self.layout.addWidget(self.run_pushButton)
            self.run_pushButton.clicked.connect(self.execCmdThreading)

    def dataReady(self,text):

        cursor = self.transcript_textEdit.textCursor()
        cursor.movePosition(cursor.End)
        cursor.insertText(str(text))
        self.transcript_textEdit.ensureCursorVisible()


    def execCmdThreading(self):
        print 'gui thread id :'+ str (QtCore.QThread.currentThreadId())
        thread = QtCore.QThread(self)
        process_inst = Process()
        process_inst.moveToThread(thread)
        process_inst.processdataReady.connect(self.dataReady)
        process_inst.processCmdDone.connect(thread.quit)
        thread.finished.connect(thread.deleteLater)
        process_inst.processCmdDone.connect(process_inst.deleteLater)
        thread.started.connect(lambda: process_inst.execCmd())
        thread.start()



if __name__=="__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindowInst = MainWindow()
    MainWindowInst.show()
    sys.exit(app.exec_())

我只是创建一个文本框和一个按钮,在单击按钮时,会为类“Process”的实例创建一个线程,该线程被移动到线程并执行其方法“execCmd”。在执行 execCmd 方法时,会发出一个信号“processdataReady”,预计会在创建的文本框中显示文本。但是,代码似乎没有按预期运行,并且 UI 确实挂了。

我真的很感谢这里的任何人的帮助。

【问题讨论】:

【参考方案1】:

您正在将 thread.started 信号连接到 lambda:

thread.started.connect(lambda: process_inst.execCmd())

在此处使用正常的 python 可调用将始终导致信号在 gui 线程中处理。这是可以理解的,因为可调用 (lambda) 不像 QObjects 那样具有线程亲和性。然后在 lambda 中同步执行 process_inst.execCmd 方法,对象具有什么线程关联性并不重要。因此GUI线程会阻塞。

如果您希望在工作线程事件lopp中接收和处理信号,请将其直接连接到插槽。

为此,您还需要确保保留对process_inst 的引用,否则它将在超出范围时被销毁。

通过这些调整,您的程序适用于我:

...
thread.started.connect(process_inst.execCmd)
thread.process_inst = process_inst
...

【讨论】:

以上是关于Qthread 不工作,GUI 仍然挂起的主要内容,如果未能解决你的问题,请参考以下文章

如何从 GUI 停止 QThread

QThread 与 QObject的关系?

为啥在 macOS 上使用 QThread 时 PyQt 应用程序崩溃或挂起?

当 GUI 冻结时显示带有 QThread 的 QMessageBox

Qthread 锁定 Gui PySide

不断更新 GUI 表单元素,使表单不会挂起