1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import os
29 import sys
30 import logging
31 import threading
32 import Queue
33
34 import settings
35
36 logger = logging.getLogger('camelot.view.model_thread')
37
38 _model_thread_ = []
39
42
44 """Decorator to ensure a function is only called from within the model
45 thread. If this function is called in another thread, an exception will be
46 thrown
47 """
48
49 def new_function(*args, **kwargs):
50 if threading.currentThread() != get_model_thread():
51 logger.error('%s was called outside the model thread' %
52 (original_function.__name__))
53 raise ModelThreadException()
54 return original_function(*args, **kwargs)
55
56 new_function.__name__ = original_function.__name__
57
58 return new_function
59
61 """Decorator to ensure a function is only called from within the gui thread.
62 If this function is called in another thread, an exception will be thrown
63
64 @todo: now it only checks if the function is not called within the model
65 thread, this is incomplete
66 """
67
68 def new_function(*args, **kwargs):
69 if threading.currentThread() == get_model_thread():
70 logger.error('%s was called outside the gui thread' %
71 (original_function.__name__))
72 raise ModelThreadException()
73 return original_function(*args, **kwargs)
74
75 new_function.__name__ = original_function.__name__
76
77 return new_function
78
79
81 """Thread in which the model runs, all requests to the model should be
82 posted to the the model thread.
83
84 This class ensures the gui thread doesn't block when the model needs
85 time to complete tasks by providing asynchronous communication between
86 the model thread and the gui thread
87 """
88
90 """@param response_signaler: an object with methods called :
91
92 responseAvailable() : this method will be called when a response is
93 available
94
95 startProcessingRequest(),
96
97 stopProcessingRequest(),
98 """
99 threading.Thread.__init__(self)
100
101 def setup_thread():
102
103
104
105
106
107
108 logger.debug('thread setup finished')
109
110 self._request_queue = Queue.Queue()
111 self._response_queue = Queue.Queue()
112 self._response_signaler = response_signaler
113 self.post(setup_thread)
114 logger.debug('model thread constructed')
115
117 logger.debug('model thread started')
118 try:
119 from settings import setup_model
120 setup_model()
121 logger.debug('start handling requests')
122 while True:
123 new_event = threading.Event()
124 try:
125 (event, request, response, exception) = self._request_queue.get()
126
127 logger.debug('start handling request')
128
129
130 self._response_signaler.startProcessingRequest()
131 result = request()
132 self._response_queue.put((new_event, result, response))
133 self._request_queue.task_done()
134 self._response_signaler.responseAvailable()
135 self._response_signaler.stopProcessingRequest()
136 logger.debug('finished handling request')
137 event.set()
138
139 except Exception, e:
140 logger.error('exception caught in model thread', exc_info=e)
141 self._response_queue.put((new_event, e, exception))
142 self._request_queue.task_done()
143 self._response_signaler.responseAvailable()
144 self._response_signaler.stopProcessingRequest()
145 event.set()
146 except:
147 logger.error('unhandled exception in model thread')
148
149 except Exception, e:
150 logger.error('exception caught in model thread', exc_info=e)
151 except:
152 logger.error('unhandled exception')
153
155 """Process all responses that are generated by completed requests
156 from the ModelThread. This method should be called from time
157 to time from within the GUI thread.
158 """
159 try:
160 while True:
161 (event, result, response) = self._response_queue.get_nowait()
162 try:
163 response(result)
164 except Exception, e:
165 logger.error('exception in response', exc_info=e)
166 self._response_queue.task_done()
167 event.set()
168 except Queue.Empty, e:
169 pass
170
171 - def post(self, request, response=lambda result:None,
172 exception=lambda exc:None):
173 """Post a request to the model thread, request should be
174 a function that takes no arguments. The request function
175 will be called within the model thread. When the request
176 is finished, on first occasion, the response function will be
177 called within the gui thread. The response function takes as
178 arguments, the results of the request function.
179 @param request: function to be called within the model thread
180 @param response: function to be called within the gui thread, when
181 the request function is finished, the response function takes
182 as its argument the result of the request function
183 @param exception: function to be called in case of an exception in the
184 request function
185 @return a threading Event object which will be set to True when the
186 request function is finished and the response has been put on the queue
187 """
188 event = threading.Event()
189 self._request_queue.put_nowait((event, request, response, exception))
190 return event
191
192 - def post_and_block(self, request):
193 """Post a request tot the model thread, block until it is finished, and
194 then return it results. This function only exists for testing purposes,
195 it should never be used from within the gui thread
196 """
197
198 self.process_responses()
199 results = []
200
201 def re_raise(exc):
202 raise exc
203
204 event = self.post(request,
205 lambda result:results.append(result),
206 exception=re_raise)
207 event.wait()
208 self.process_responses()
209 return results[-1]
210
213
216