[PyQt] GIL related problem? Code inside.

V. Armando Sole sole at esrf.fr
Thu Apr 19 13:37:02 BST 2007


Phil Thompson wrote:

"""
3. Automatically release the GIL whenever any Qt call is made from a QObject
derived class. The assumption is that this will cover the vast majority of
cases. Any other cases would have to be identified through bug reports.
"""

I guess I have one bug report.

We have got some problems with old PyQt3 applications when upgrading to the 
latest sip and PyQt. The problem comes from the new GIL handling and the 
use of QApplication.postEvent inside QThreads.

While we agree on the above described behaviour, perhaps an exception 
should be made for qt.QApplication.postEvent (at least from inside a 
QThread). The cleanest way we found to solve our problem is to replace 
every appearence of QApplication.postEvent by QThread.postEvent if the call 
is made from inside a qthread. At the qt level both calls are exactly the 
same, but Trolltech says in the documentation that  QThread.postEvent is 
obsolete.

The code below reproduces the problem if desired and shows the solution too.

Best regards,

Armando

# --- BEGIN OF CODE ----
import sys

if len(sys.argv) > 1:
     option = int(sys.argv[1])
else:
     print "Usage:"
     print "python PostEventDeadlock.py number"
     print "number = 0 -> Use PyQt4"
     print "number = 1 -> Use PyQt without crashing"
     print "number = 2 -> Use PyQt and crash"
     sys.exit(0)
     import qt

IWILLCRASH = False
if option == 1:
     import qt
elif option == 2:
     import qt
     IWILLCRASH = True
else:
     import PyQt4.Qt as qt

QTVERSION = qt.qVersion()

if QTVERSION > '4.0.0':
     MYEVENT = qt.QEvent.User
     class MyCustomEvent(qt.QEvent):
         def __init__(self, ddict={}):
             self.dict = ddict
             qt.QEvent.__init__(self, MYEVENT)

         def type(self):
             print "called"
             return MYEVENT
else:
     MYEVENT = qt.QEvent.User + 1

     class MyCustomEvent(qt.QCustomEvent):
         def __init__(self, ddict={}):
             qt.QCustomEvent.__init__(self, MYEVENT)
             self.dict = ddict


class CounterWidget(qt.QWidget):
     def __init__(self):
         qt.QWidget.__init__(self)
         self.count = 0

         self.layout = qt.QHBoxLayout(self)

         self.label = qt.QLabel(self)
         self.label.setText("COUNT: %d" % self.count)

         self.layout.addWidget(self.label)

         self.stock = []
         for i in range(10):
             thread = CounterThread(self)
             thread.start()
             self.stock.append(thread)

     def customEvent(self,event):
         self.count += 1
         self.label.setText("COUNT: %d" % self.count)
         self.label.update()

if QTVERSION < '4.0.0':
     class CounterThread(qt.QThread):
         def __init__(self, receiver):
             qt.QThread.__init__(self)
             self.object = qt.QObject()
             self.receiver = receiver

         def run(self):
             self.work_loop()

         def work_loop(self):
             for x in xrange(500):
                 self.postTheEvent(x)
                 self.msleep(1)

         def postTheEvent(self, value):
             ddict = {'value': value}
             if IWILLCRASH:
                 #this crashes
                 qt.QApplication.postEvent(self.receiver, MyCustomEvent(ddict))
             else:
                 #this does not crash
                 self.postEvent(self.receiver, MyCustomEvent(ddict))

else:
     class CounterThread(qt.QThread):
         def __init__(self, receiver = None):
             qt.QThread.__init__(self)
             self.receiver = receiver

         def run(self):
             if 0:
                 #This does not update the label
                 qt.QTimer.singleShot(0, self.work_loop)
                 self.exec_()
             else:
                 #This is fine
                 self.work_loop()

         def work_loop(self):
             for x in xrange(500):
                 self.postTheEvent(x)
                 self.msleep(1)

         def postTheEvent(self, value):
             ddict = {'value': value}
             qt.QApplication.postEvent(self.receiver, MyCustomEvent(ddict))

if __name__ == "__main__":
     app = qt.QApplication([])
     w = CounterWidget()
     w.show()
     app.connect(app, qt.SIGNAL('lastWindowClosed()'),
                 app, qt.SLOT('quit()'))
     if QTVERSION  < '4.0.0':
         app.exec_loop()
     else:
         app.exec_()

# --- END OF CODE ----




More information about the PyQt mailing list