2016-08-02 48 views
1

我一直在寻找解决方案在stackoverflow和其他pyqt教程中如何克服pyqt4中的GUI冻结问题。有类似的话题提出了以下方法来纠正它: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次后才能看到输出。我不太满意,因为它有时会在第5次迭代时跳过打印,并且它会打印出值迭代7接下来)一些关于如何在这个目的使用线程的指导将不胜感激。

+0

如果你的主线程中的方法没有立即返回,那么在后台运行的Qt事件循环会被阻塞,并且GUI会冻结。你的主线程中有一个while循环,用于串行读写。有可能这是阻止你的主线程。将整个事物卸载到一个或多个线程。 –

回答

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看起来很敏感。但是我在GUI中发现一个小故障,因为我不确定是否因为程序运行速度比刷新帧所需的时间快得多。我觉得这是因为放置在GUI中的计数器在更新值时跳过一两个计数。但是GUI是响应并且在执行程序期间有没有强制关闭。

希望这可以帮助那些寻找类似问题的人。对故障和良好的编程技术有更多的了解是值得欢迎的。谢谢。