00001
00002
00003
00004
00005
00006
00007
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
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
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
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
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
00142
00143 respQ = self.__respQueues[currentThread]
00144 self.__pyBridge.pushEvent((GUIbridge.GUI_CMD_FUNC, qObject,
00145 (func,) + args + (kwargs, respQ)))
00146
00147 return self.__pyBridge.pullResponse(respQ)
00148
00149 def handleCustomEvent(self, e):
00150 try:
00151
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
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)
00196 except Exception, e:
00197 log.exception(e)
00198
00199
00200 tplEvent = None
00201 tplEventType = None
00202 tplEventDest = None
00203 tplEventData = None
00204
00205
00206
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
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
00256 gb.execGUImethodNR(qo, tf.setText, string)
00257
00258
00259
00260
00261 v = gb.execGUImethod(qo, tf.text)
00262
00263
00264
00265
00266
00267 if str(v) != string: print "Thread %d: '%s' != '%s'" % (id, string, v)
00268
00269
00270 if id == 1:
00271 count += 1
00272 time.sleep(0.123)
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)
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)
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
00330 startTest(sys.argv)