python - PyQt4:GUI 在长时间运行的循环中卡住

标签 python multithreading qt user-interface pyqt4

我一直在 stackoverflow 和其他 pyqt 教程中寻找有关如何克服 pyqt4 中的 GUI 卡住问题的解决方案。有类似的主题建议使用以下方法来纠正它:

  • 将长时间运行的循环移至辅助线程,绘制 GUI 发生在主线程中。
  • 在循环中调用 app.processEvents()。这使 Qt 有机会处理事件并重绘 GUI。

我已经尝试了上述方法,但我的 GUI 仍然卡住。我在下面给出了导致问题的代码结构。

# a lot of headers
from PyQt4 import QtCore, QtGui
import time
import serial
from time import sleep
from PyQt4.QtCore import QThread, SIGNAL

getcontext().prec = 6
getcontext().rounding = ROUND_CEILING

adbPacNo = 0
sdbPacNo =0
tmPacNo = 0

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

#ADB Widget

class Ui_ADB(object):

    def setupUi(self, ADB):
        ADB.setObjectName(_fromUtf8("ADB"))
        ADB.resize(1080, 212)
        self.gridLayout_2 = QtGui.QGridLayout(ADB)
        self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.label_20 = QtGui.QLabel(ADB)
        font = QtGui.QFont()
        font.setBold(True)
        font.setUnderline(True)
        font.setWeight(75)
        self.label_20.setFont(font)
        self.label_20.setAlignment(QtCore.Qt.AlignCenter)
        self.label_20.setObjectName(_fromUtf8("label_20"))
        .
        # Rate X
        self.rateX = QtGui.QLineEdit(ADB)
        self.rateX.setReadOnly(True)
        self.rateX.setObjectName(_fromUtf8("rateX"))
        self.gridLayout.addWidget(self.rateX, 1, 6, 1, 1)
        # Rate Z
        self.rateZ = QtGui.QLineEdit(ADB)
        self.rateZ.setReadOnly(True)
        self.rateZ.setObjectName(_fromUtf8("rateZ"))
        self.gridLayout.addWidget(self.rateZ, 1, 10, 1, 1)

        # Rate Y
        self.rateY = QtGui.QLineEdit(ADB)
        self.rateY.setReadOnly(True)
        self.rateY.setObjectName(_fromUtf8("rateY"))
        self.gridLayout.addWidget(self.rateY, 1, 8, 1, 1)
        # qv2

        # qv1

        # rateValid

        # qv3

        # qs

        # and a lot more....

    def retranslateUi(self, ADB):
        # this contains the label definintions

# SDB Widget
class Ui_SDB(object):
    def setupUi(self, SDB):
        # again lot of fields to be displayed

    def retranslateUi(self, SDB):
        # this contains the label definintions

    def sdbReader(self, sdbData):
    #--- CRC Checking -------------------------------------------------#
        global sdbPacNo
        sdbPacNo+=1
        tmCRC = sdbData[0:4];
        data = sdbData[4:];
        tmCRCResult = TM_CRCChecker(data,tmCRC)
        if (tmCRCResult == 1):
            print 'SDB Packet verification : SUCCESS!'
        else:
            print 'SDB packet verification : FAILED!'
            quit()

    #--- Type ID and Length -------------------------------------------#

        # code to check the ID and length of the packet

    #--- Reading out SDB into its respective variables ----------------#
    # the code that performs the calculations and updates the parameters for GUI



## make thread for displaying ADB and SDB separately

# ADB Thread
class adbThread(QThread):
    def __init__(self,Ui_ADB, adbData):
        QThread.__init__(self)
        self.adbData = adbData
        self.Ui_ADB = Ui_ADB

    def adbReader(self,adbData):
        global adbPacNo
        adbPacNo+=1;
#--- CRC Checking -------------------------------------------------#
        tmCRC = self.adbData[0:4];
        data = self.adbData[4:];
        tmCRCResult = TM_CRCChecker(data,tmCRC)
        if (tmCRCResult == 1):
            print 'ADB Packet verification : SUCCESS!'
        else:
            print 'ADB packet verification : FAILED!'

#--- Type ID and Length -------------------------------------------#
    # code to check the ID and length

#--- Reading out ADB into respective variables --------------------#
        qvUnit = decimal.Decimal(pow(2,-30))
        qv1 = qvUnit*decimal.Decimal(int(ADBlock[0:8],16))
        qv1 = qv1.to_eng_string()
        print 'qv1 = '+ qv1
        self.Ui_ADB.qv1.setText(qv1)

        # similar to above code there are many such variables that have to
        # be calculated and printed on the respective fields.

    def __del__(self):
        self.wait()

    def run(self):
        self.adbReader(self.adbData)
        myMessage = "ITS F** DONE!"
        self.emit(SIGNAL('done(QString)'), myMessage)
        print "I am in ADB RUN"


# SDB Thread
class sdbThread(QThread):
#similar type as of adbThread

# Global Variable to set the number of packets
packets=0

class mainwindow(QtGui.QMainWindow):

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(1153, 125)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.formLayout = QtGui.QFormLayout(self.centralwidget)
        self.formLayout.setObjectName(_fromUtf8("formLayout"))
        self.label = QtGui.QLabel(self.centralwidget)
        self.label.setObjectName(_fromUtf8("label"))
        self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label)
        self.serialStatus = QtGui.QLineEdit(self.centralwidget)
        self.serialStatus.setReadOnly(True)
        self.serialStatus.setObjectName(_fromUtf8("serialStatus"))
        self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.serialStatus)
        self.label_2 = QtGui.QLabel(self.centralwidget)
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2)
        self.lineEdit = QtGui.QLineEdit(self.centralwidget)
        self.lineEdit.setReadOnly(True)
        self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
        self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.lineEdit)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1153, 25))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        ################################################################

        #Setting up ADB
        self.Ui_ADB = Ui_ADB()
        self.myADB = QtGui.QWidget()
        self.Ui_ADB.setupUi(self.myADB)
        self.myADB.show()

        # Setting up SDB
        self.Ui_SDB = Ui_SDB()
        self.mySDB = QtGui.QWidget()
        self.Ui_SDB.setupUi(self.mySDB)

        # Setting up the serial communication
        self.tmSerial = serial.Serial('/dev/ttyACM0',9600)

        self.sdb_Thread = sdbThread(self.Ui_SDB, self.mySDB)        

        buff = ''
        tempByte= ''

        counter =1

        while counter<10:
            # this reads the header of the SP 

            # Simulating the RTT signal trigger
            self.tmSerial.write('y')
            print "serial opened to read header"
            tmSerialData = self.tmSerial.read(8*8)
            print "tmSerialData="+str(tmSerialData)
            littleEndian = tmSerialData[0:8*8]

            # Converts the bitstream of SP header after converting to bigEndian 
            bufferData = bitstream_to_hex(littleEndian)
            print "bufferData="+str(bufferData)

            # Reads the header info : First 8 bytes
            headerINFO = readHeader(bufferData)

            # checking the packets in the headerINFO
            # ADB & SDB present
            global tmPacNo
            if (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 1):
                print 'Both ADB and SDB info are present'
                tmPacNo+=1;

                # Need to call both ADB and SDB 
                # Statements for reading the ADB
                bufferData = tmSerial.read(42*8) # ADB packet bitstream
                self.adbPacket = bitstream_to_hex(bufferData)

                # Calling ADB thread
                self.adb_Thread = adbThread(self.Ui_ADB, self.adbPacket)
                self.adb_Thread.start()
                #self.connect(self.adb_Thread, SIGNAL("finished()"),self.done)
                self.connect(self.adb_Thread, SIGNAL("done(QString)"), self.done)
                QtGui.QApplication.processEvents()

                # IGNORED FOR NOW...
                ## Statements for reading the SDB 
                #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
                #self.sdbPacket = bitstream_to_hex(bufferData)

                ## Calling SDB thread

                #self.sdb_Thread.run(self.sdbPacket)


            elif (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 0):
                print 'ADB INFO only present'
                tmPacNo+=1;

                # Statements for reading the ADB
                bufferData = self.tmSerial.read(42*8) # ADB packet bitstream
                self.adbPacket = bitstream_to_hex(bufferData)
                # Calling ADB thread
                self.adb_Thread = adbThread(self.Ui_ADB, self.adbPacket)
                self.adb_Thread.start()
                #self.connect(self.adb_Thread, SIGNAL("finished()"),self.done)
                self.connect(self.adb_Thread, SIGNAL("done(QString)"), self.done)
                QtGui.QApplication.processEvents()

            # IGNORED FOR NOW...
            #elif (headerINFO['adbINFO'] == 0 and headerINFO['sdbINFO'] == 1):
                #print 'SDB INFO only present'
                #tmPacNo+=1;
                ## Statements for reading the SDB
                #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
                #self.sdbPacket = bitstream_to_hex(bufferData)
                ## Calling SDB thread

                #self.sdb_Thread.run(sdbPacket)

            #while (self.adb_Thread.isFinished() or self.sdb_Thread.isFinished() is False):
                #print "waiting to complete adb Thread"

            counter+=1

        ################################################################

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.label.setText(_translate("MainWindow", "Serial Communication Status", None))
        self.label_2.setText(_translate("MainWindow", "No. of SP_Packets Received", None))

    ####################################################################
    def done(self,someText):
        print someText + "the value has been updated"
        self.myADB.show()



# This program converts the little endian bitstream -> BigEndian -> hex
def bitstream_to_hex(bitStream):
    #global littleEndian
    # small code for conversion


if __name__== "__main__":
    import sys

    # setting up the GUI
    app = QtGui.QApplication(sys.argv)
    main = mainwindow()
    main.show() 
    sys.exit(app.exec_())

在上面的代码中可以注意到线程已经被实现,但我不确定我做错了什么?我已将长时间运行的循环 adbreader() 放入线程中,但 GUI 中的值并未响应更新。我只能在 while 循环运行 10 次后才能查看输出。

此外,我尝试使用QtGui.QApplication.processEvents(),这以某种方式设法在 GUI 中打印值,但我对这种方法不满意。(不高兴是因为,有时在迭代 5 时跳过打印,并在接下来的迭代 7 中打印值)非常感谢有关如何为此目的使用线程的一些指导。

最佳答案

根据three_pinapples的建议,我尝试通过创建更多线程来卸载程序。此外,我还调用了在 while 循环中执行整个串行写入和读取的线程。这导致了无论循环如何都只能调用线程一次的问题。我不确定为什么,但我想这可能是因为在循环中一次又一次地调用同一个对象?不确定。

我找到了解决这个问题的方法,即使用信号/槽机制作为递归函数,使线程保持无限运行模式,而不管 while 循环如何。我已经发布了以下代码的修改结构:

# a lot of headers
from PyQt4 import QtCore, QtGui
import time
import serial
from time import sleep
from PyQt4.QtCore import QThread, SIGNAL

getcontext().prec = 6
getcontext().rounding = ROUND_CEILING

adbPacNo = 0
sdbPacNo =0
tmPacNo = 0

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

#ADB Widget

class Ui_ADB(object):

    def setupUi(self, ADB):


        # Rate X

        # Rate Z


        # Rate Y


        # qv2

        # qv1

        # rateValid

        # qv3

        # qs

        # and a lot more....

    def retranslateUi(self, ADB):
        # this contains the label definintions


## make thread for displaying ADB and SDB separately

# ADB Thread
class adbThread(QThread):
    def __init__(self,Ui_ADB, adbData):


    def adbReader(self,adbData):
        global adbPacNo
        adbPacNo+=1;
#--- CRC Checking -------------------------------------------------#


#--- Type ID and Length -------------------------------------------#
    # code to check the ID and length

#--- Reading out ADB into respective variables --------------------#


        # similar to above code there are many such variables that have to
        # be calculated and printed on the respective fields.

    def __del__(self):
        self.wait()

    def run(self):
        self.adbReader(self.adbData)
        myMessage = "ITS F** DONE!"
        self.emit(SIGNAL('done(QString)'), myMessage)
        print "I am in ADB RUN"


# SDB Thread
class sdbThread(QThread):
#similar type as of adbThread

# Global Variable to set the number of packets
packets=0

# WorkerThread : This runs individually in the loop & call the respective threads to print.
class workerThread(QThread):

    readComplete = QtCore.pyqtSignal(object)

    def __init__(self, tmSerial, Ui_ADB, myADB, Ui_SDB, mySDB):
        QThread.__init__(self)
        self.tmSerial = tmSerial
        self.Ui_ADB = Ui_ADB
        self.myADB = myADB
        self.Ui_SDB = Ui_SDB
        self.mySDB = mySDB

    def __del__(self):
        self.wait()

    def run(self):
        print "worker = "+str(self.temp)
        buff = ''
        tempByte= ''

        # Simulating the RTT signal trigger

        self.tmSerial.write('y')

        # Reading SP Header
        tmSerialData = self.tmSerial.read(8*8)

        # Converts the bitstream of SP header after converting to bigEndian 
        bufferData = bitstream_to_hex(littleEndian)

        # Reads the header info : First 8 bytes
        headerINFO = readHeader(bufferData)

        # checking the packets in the headerINFO

        global tmPacNo
        if (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 1):
            print 'Both ADB and SDB info are present'
            tmPacNo+=1;

            # Need to call both ADB and SDB 
            # Statements for reading the ADB
            bufferData = tmSerial.read(42*8) # ADB packet bitstream
            self.adbPacket = bitstream_to_hex(bufferData)

            # Calling ADB thread
            self.adb_Thread = adbThread(self.Ui_ADB, self.myADB, self.adbPacket)
            self.adb_Thread.start()
            self.adb_Thread.adbReadComplete.connect(self.adbdone)

            # IGNORED -- Statements for reading the SDB


            # Calling SDB thread

            #self.sdb_Thread.run(self.sdbPacket)


        elif (headerINFO['adbINFO'] == 1 and headerINFO['sdbINFO'] == 0):
            print 'ADB INFO only present'
            tmPacNo+=1;

            # Statements for reading the ADB
            bufferData = self.tmSerial.read(42*8) # ADB packet bitstream
            self.adbPacket = bitstream_to_hex(bufferData)
            # Calling ADB thread
            self.adb_Thread = adbReadThread(self.Ui_ADB, self.myADB , self.adbPacket)
            self.adb_Thread.start()
            self.adb_Thread.adbReadComplete.connect(self.adbDone)

        # IGNORED FOR NOW
        #elif (headerINFO['adbINFO'] == 0 and headerINFO['sdbINFO'] == 1):
            #print 'SDB INFO only present'
            #tmPacNo+=1;
            ## Statements for reading the SDB
            #bufferData = self.tmSerial.read(46*8) # SDB packet bitstream
            #self.sdbPacket = bitstream_to_hex(bufferData)
            ## Calling SDB thread

            #self.sdb_Thread.run(sdbPacket)
        mess = "Worker Reading complete"

        self.readComplete.emit(mess)


    def adbDone(self,text):
        print text
        #self.myADB.show()



# Global Variable to set the number of packets
packets=0

class mainwindow(QtGui.QMainWindow):

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(1153, 125)
        # ..... codes for main window GUI

        ################################################################

        #Setting up ADB
        self.Ui_ADB = Ui_ADB()
        self.myADB = QtGui.QWidget()
        self.Ui_ADB.setupUi(self.myADB)
        #self.myADB.show()

        # IGONRED FOR NOW -- Setting up SDB
        self.Ui_SDB = Ui_SDB()
        self.mySDB = QtGui.QWidget()
        self.Ui_SDB.setupUi(self.mySDB)

        # Setting up the serial communication
        self.tmSerial = serial.Serial('/dev/ttyACM0',9600)

        #  IGONRED FOR NOW --  setting up the SDB read thread 
        #self.sdb_Thread = sdbReadThread(self.Ui_SDB, self.SDBPacket)

        # *** MODIFIED ***
        # Setting up the Worker thread 
        self.tmWorker = workerThread(self.tmSerial, self.Ui_ADB, self.myADB, Ui_SDB, self.mySDB)

        # Code to call the thread that checks the serial data and print accordingly

        self.tmWorker.start()
        self.tmWorker.readComplete.connect(self.done) # This will act as a recursive function


    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.label.setText(_translate("MainWindow", "Serial Communication Status", None))
        self.label_2.setText(_translate("MainWindow", "No. of SP_Packets Received", None))

    ####################################################################
    def done(self):
        print "worker reading done" 
        self.myADB.show()
        self.tmWorker.start() #Modified
        #sleep(01)

# This program converts the little endian bitstream -> BigEndian -> hex
def bitstream_to_hex(bitStream):
    # Code for conversion

if __name__== "__main__":
    import sys

    # setting up the GUI
    app = QtGui.QApplication(sys.argv)
    main = mainwindow()
    main.show()
    sys.exit(app.exec_())

该程序现在运行良好,并且 GUI 似乎响应良好。但我发现 GUI 中存在一个问题,因为我不确定是否是因为程序运行速度比刷新帧所需的时间快得多。我发现这是因为放置在 GUI 中的计数器在更新值时会跳过一到两次计数。但 GUI 是响应的,并且在程序执行期间没有强制关闭。

希望这对正在寻找类似问题的人有所帮助。欢迎对故障和良好的编程技术有更多见解。谢谢。

关于python - PyQt4:GUI 在长时间运行的循环中卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38722219/

相关文章:

multithreading - 在主线程执行完成后, Spring 批处理线程不返回

python - 控制 QToolBar 中纯文本按钮的大小

c++ - Qt 中的 Tab 键顺序

python - 如何在 Python 中追加一个 json 文件?

python - 如何在 Keras 中的多元多步 LSTM 实现中标准化(和反转预测)

Java Thread——同步线程

c++ - 如何在文本突出显示期间保留语法突出显示

python - 如何在没有 Visual Studio 的 Windows 上安装 leptonica+tesseract 以在 Anaconda 中使用?

Python CPU 和 RAM 内存使用情况

java - Java 如何利用多核?