Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | Related Pages

guiBridges.py

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

Generated on Fri Jul 21 13:26:29 2006 for LATTE R04-12-00 by doxygen 1.4.3