guiBridges.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 #                               Copyright 2005
00004 #                                     by
00005 #                        The Board of Trustees of the
00006 #                     Leland Stanford Junior University.
00007 #                            All rights reserved.
00008 #
00009 
00010 __facility__ = "Online"
00011 __abstract__ = "Classes for launching Qt objects from non-GUI threads"
00012 __author__   = "S. Tuvi <stuvi@SLAC.Stanford.edu> SLAC - GLAST LAT I&T/Online"
00013 __date__     = "2004/03/09 00:00:00"
00014 __updated__  = "$Date: 2005/09/22 00:29:31 $"
00015 __version__  = "$Revision: 1.5 $"
00016 __release__  = "$Name: HEAD $"
00017 __credits__  = "SLAC"
00018 
00019 import LICOS.copyright_SLAC
00020 
00021 import logging       as     log
00022 from   qt            import *
00023 from   Queue         import Queue
00024 import threading
00025 import sys
00026 
00027 
00028 class GUIbridge(object):
00029   """!\brief Class for providing a bridge between the GUI thread and other threads.
00030 
00031   This bridge is needed whenever a non-GUI thread needs to interact with the
00032   GUI.  The GUI is handled by the 'GUI thread'.  A 'GUI thread' is defined to
00033   be the one in which the Qt QApplication class's exec_loop() method is
00034   executed.  There can be only one GUI thread in a program.
00035 
00036   The solution to the problem is handled by sending a message (an 'event', in
00037   Qt terms) from the non-GUI thread to the GUI thread.  The message contains
00038   information on how it is to be handled.  In some cases, this handling produces
00039   a result that must be sent back to the initiating non-GUI thread.  The
00040   requests and responses are passed around via queues.  Separate queues are
00041   maintained for the different non-GUI threads so that responses don't end up
00042   handed off to the wrong thread.
00043   """
00044   GUI_CMD_INST    = QEvent.Type(QEvent.User + 1)
00045   GUI_CMD_FUNC    = QEvent.Type(QEvent.User + 2)
00046   GUI_CMD_FUNC_NR = QEvent.Type(QEvent.User + 3)
00047 
00048   def __init__(self, guiThread):
00049     """!\brief This is the constructor of the GUIbridge class.
00050 
00051     It must be called from the GUI thread.
00052     """
00053     self.__guiThread  = guiThread
00054     self.__cmdQueue   = Queue()
00055     self.__respQueues = {}
00056     self.__qtBridge   = QtThreadBridge("QtBridge", self.__cmdQueue)
00057     self.__qtBridge.start()
00058     self.__pyBridge   = PyThreadBridge("PyBridge", self.__cmdQueue)
00059 
00060   def shutdown(self):
00061     """!\brief This method is used to shut the GUI bridge down.
00062     """
00063     self.__qtBridge.quit()
00064 
00065   def isGUIthread(self):
00066     """!\brief Returns True if this method is being called from the GUI thread.
00067     """
00068     return threading.currentThread() == self.__guiThread
00069 
00070   def createGUI(self, qObject, obj, *args, **kwargs):
00071     """!\brief Method used for instantiating a GUI from a non-GUI thread.
00072 
00073     \param qObject - The Qt object that will receive the customEvent callback
00074     \param obj     - The GUI object class type to create
00075     \param args    - Arguments for the object class constructor
00076     \param kwargs  - Keyword arguments for the object class constructor
00077     \return An instance of the requested GUI object class
00078     """
00079     # Don't use the GUI bridge if we're being called from the GUI thread
00080     if self.isGUIthread():  return apply(obj, args, kwargs)
00081 
00082     currentThread = threading.currentThread()
00083     if currentThread not in self.__respQueues:
00084       self.__respQueues[currentThread] = Queue()
00085 
00086     respQ = self.__respQueues[currentThread]
00087     self.__pyBridge.pushEvent((GUIbridge.GUI_CMD_INST, qObject,
00088                                (obj,) + args + (kwargs, respQ)))
00089     return self.__pyBridge.pullResponse(respQ)
00090 
00091   def execGUImethodNR(self, qObject, func, *args, **kwargs):
00092     """!\brief Method used for executing a GUI function from a non-GUI thread.
00093 
00094     Use this method when no (useful) response is expected or
00095     when waiting for one could cause a deadlock.  Any response from the
00096     function is lost.
00097     \param qObject - The Qt object that will receive the customEvent callback
00098     \param func    - The GUI function or method to call
00099     \param args    - Arguments for the function or method
00100     \param kwargs  - Keyword arguments for the function or method
00101     """
00102     if not self.isGUIthread():
00103       respQ = None                      # No response expected or required
00104       self.__pyBridge.pushEvent((GUIbridge.GUI_CMD_FUNC_NR, qObject,
00105                                  (func,) + args + (kwargs, respQ)))
00106     else:
00107       apply(func, args, kwargs)
00108 
00109   def execGUImethod(self, qObject, func, *args, **kwargs):
00110     """!\brief Method used for executing a GUI function from a non-GUI thread.
00111 
00112     Use this method when a response is expected from the function and
00113     when it is appropriate to wait for it (no deadlock arises)
00114     \param qObject - The Qt object that will receive the customEvent callback
00115     \param func    - The GUI function or method to call
00116     \param args    - Arguments for the function or method
00117     \param kwargs  - Keyword arguments for the function or method
00118     \return The called GUI function's return value
00119     """
00120     # Don't use the GUI bridge if we're being called from the GUI thread
00121     if self.isGUIthread():  return apply(func, args, kwargs)
00122 
00123     currentThread = threading.currentThread()
00124     if currentThread not in self.__respQueues:
00125       self.__respQueues[currentThread] = Queue()
00126 
00127     respQ = self.__respQueues[currentThread]
00128     self.__pyBridge.pushEvent((GUIbridge.GUI_CMD_FUNC, qObject,
00129                                (func,) + args + (kwargs, respQ)))
00130     return self.__pyBridge.pullResponse(respQ)
00131 
00132   if __name__ == "__main__":
00133     def execGUImethod2(self, qObject, func, *args, **kwargs):
00134       # Don't use the GUI bridge if we're being called from the GUI thread
00135       if self.isGUIthread():  return apply(func, args, kwargs)
00136 
00137       currentThread = threading.currentThread()
00138       if currentThread not in self.__respQueues:
00139         if '_GUIbridge__respQueue' not in self.__dict__:
00140           self.__respQueue = Queue()
00141         self.__respQueues[currentThread] = self.__respQueue #Queue()
00142 
00143       respQ = self.__respQueues[currentThread]
00144       self.__pyBridge.pushEvent((GUIbridge.GUI_CMD_FUNC, qObject,
00145                                  (func,) + args + (kwargs, respQ)))
00146       #time.sleep(0.01)                    # Delay to allow a task switch
00147       return self.__pyBridge.pullResponse(respQ)
00148 
00149   def handleCustomEvent(self, e):
00150     try:
00151       # This method overrides the QObject base class's
00152       data = e.data()
00153       func     = data[0]
00154       args     = data[1:-3]
00155       kwargs   = data[-3]
00156       respQ    = data[-2]
00157       qtBridge = data[-1]
00158       if e.type() == GUIbridge.GUI_CMD_INST:
00159         qtBridge.pushResponse(respQ, func(*args, **kwargs))
00160       elif e.type() == GUIbridge.GUI_CMD_FUNC:
00161         qtBridge.pushResponse(respQ, func(*args, **kwargs))
00162       elif e.type() == GUIbridge.GUI_CMD_FUNC_NR:
00163         func(*args, **kwargs)
00164     except Exception, exc:
00165       log.exception("GUIbridge.handleCustomEvent(): Caught exception")
00166       if e.type() == GUIbridge.GUI_CMD_INST or \
00167          e.type() == GUIbridge.GUI_CMD_FUNC:
00168         qtBridge.pushResponse(respQ, None, exc)
00169 
00170 
00171 class QtThreadBridge(QThread):
00172   def __init__(self, name, cmdQueue):
00173     QThread.__init__(self)
00174     self._cmdQueue = cmdQueue
00175     self.__quit = False
00176 
00177   def quit(self):
00178     self.__quit = True
00179     self._cmdQueue.put( (None) )
00180 
00181   def pushResponse(self, respQueue, event, exception=None):
00182     respQueue.put((event, exception))
00183 
00184   def run(self):
00185     while not self.__quit:
00186       try:
00187         tplEvent = self._cmdQueue.get(True)
00188         if tplEvent is None:  break     # Quit signal is None
00189         tplEventType = tplEvent[0]
00190         tplEventDest = tplEvent[1]
00191         tplEventData = tplEvent[2]
00192         evt = QCustomEvent(tplEventType)
00193         evt.setData(tplEventData + (self,))
00194         QApplication.postEvent(tplEventDest, evt)
00195         self.msleep(1)                  # RiC: Why's this here?
00196       except Exception, e:
00197         log.exception(e)
00198 
00199       # Drop references to this object to allow it to be garbage collected
00200       tplEvent     = None
00201       tplEventType = None
00202       tplEventDest = None
00203       tplEventData = None
00204 
00205     # logging already shut down?
00206     #log.info(self.__class__.__name__ + ": terminating")
00207 
00208 
00209 class PyThreadBridge(object):
00210   def __init__(self,name, cmdQueue):
00211     self._cmdQueue = cmdQueue
00212 
00213   def pushEvent(self, event):
00214     try:
00215       self._cmdQueue.put(event)
00216     except Exception, e:
00217       log.exception(e)
00218 
00219   def pullResponse(self, respQueue, blnBlock = 1):
00220     try:
00221       response, exception = respQueue.get(blnBlock)
00222       if exception is None:  return response
00223       raise exception
00224     except Exception, e:
00225       log.exception(e)
00226     #  raise e
00227 
00228 
00229 if __name__ == "__main__":
00230   import time
00231 
00232   class GUIbridgeTest(threading.Thread):
00233     def __init__(self, qObject, guiBridge, id, label):
00234       threading.Thread.__init__(self)
00235 
00236       self.__qObject   = qObject
00237       self.__guiBridge = guiBridge
00238       self.__id        = id
00239       self.__label     = label
00240       self.__quit      = False
00241 
00242     def shutdown(self):
00243       self.__quit  = True
00244 
00245     def run(self):
00246       qo = self.__qObject
00247       gb = self.__guiBridge
00248       tf = self.__label
00249       id = self.__id
00250       count = 0
00251       start = time.time()
00252       while not self.__quit:
00253         string = "Thread %d: %12d" % (id, count)
00254 
00255         # Write value
00256         gb.execGUImethodNR(qo, tf.setText, string)
00257 
00258         # Old method:
00259         #v = gb.execGUImethod2(qo, tf.text)
00260         # New method:
00261         v = gb.execGUImethod(qo, tf.text)
00262 
00263         # Bypass GUI thread:
00264         #v = tf.text("Thread %d: %d" % (id, count)) # This doesn't reliably work
00265 
00266         # Compare result
00267         if str(v) != string:  print "Thread %d: '%s' != '%s'" % (id, string, v)
00268 
00269         #time.sleep(.001)
00270         if id == 1:
00271           count += 1
00272           time.sleep(0.123) # To get some beating betwee the two tasks
00273         else:        count -= 1
00274       end  = time.time()
00275       if id == 2:  count = -count
00276       rate = count / (end - start)
00277       print "Exiting thread %d, %d calls = %.1f setText()s per second" % \
00278             (self.__id, count, rate)
00279 
00280 
00281   class TestWindow(QMainWindow):
00282     def __init__(self, parent = None, name = None, fl = 0):
00283       QMainWindow.__init__(self, parent, name, fl)
00284 
00285       self.setName("TestWindow")
00286 
00287       self.mainWidget = QWidget(self) # dummy widget to containt the layout mgr
00288       self.setCentralWidget(self.mainWidget)
00289       layout = QVBoxLayout(self.centralWidget(), 11, 6, "Test_Window_Layout")
00290 
00291       self.__label1 = QLabel("Thread 1:", self.mainWidget)
00292       self.__label2 = QLabel("Thread 2:", self.mainWidget)
00293       self.__quit   = QPushButton("Quit", self.mainWidget, "quit")
00294       self.connect(self.__quit, SIGNAL("clicked()"), self.quit)
00295 
00296       layout.addWidget(self.__label1)
00297       layout.addWidget(self.__label2)
00298       layout.addWidget(self.__quit)
00299 
00300       self.__guiBridge = GUIbridge(threading.currentThread())
00301 
00302       self.__gbt1 = GUIbridgeTest(self, self.__guiBridge, 1, self.__label1)
00303       self.__gbt2 = GUIbridgeTest(self, self.__guiBridge, 2, self.__label2)
00304 
00305       self.__gbt1.start()
00306       self.__gbt2.start()
00307 
00308     def customEvent(self, e):
00309       self.__guiBridge.handleCustomEvent(e)
00310 
00311     def quit(self):
00312       self.__gbt1.shutdown()
00313       self.__gbt2.shutdown()
00314       time.sleep(0.1) # Seems to be required to yield and allow things to exit
00315       self.__guiBridge.shutdown()
00316       self.close()
00317 
00318   def startTest(args):
00319     app = QApplication(args)
00320     app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
00321 
00322     win = TestWindow()
00323     app.setMainWidget(win)
00324     win.show()
00325 
00326     app.exec_loop()
00327 
00328 
00329   # Start the test
00330   startTest(sys.argv)

Generated on Thu Apr 27 20:52:41 2006 for LICOS L02-01-00 by doxygen 1.4.6-NO