PyQt4:在长时间运行的循环中卡住了

时间:2016-08-02 13:44:38

标签: 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中的值next)非常感谢有关如何在此目的中使用线程的一些指导。

1 个答案:

答案 0 :(得分:1)

正如three_pinapples所建议的,我试图通过创建更多线程来卸载程序。我进一步调用thread执行整个序列写入和读取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似乎响应。但是我发现GU​​I中存在一个小故障,因为我不确定是否因为程序运行速度比刷新帧所需的时间快得多。我发现它是因为放置在GUI中的计数器在更新值时跳过一个或两个计数。但是GUI 响应并且在程序执行期间有没有强制关闭。

希望这有助于寻找类似问题的人。欢迎更多关于毛刺和良好编程技术的见解。谢谢。