source: sasview/guiframe/calcthread.py @ ff7acff

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ff7acff was ca88b2e, checked in by Gervaise Alina <gervyh@…>, 16 years ago

working on history panel

  • Property mode set to 100644
File size: 11.4 KB
Line 
1# This program is public domain
2
3## \file
4#  \brief Abstract class for defining calculation threads.
5#
6
7import thread, traceback
8
9import sys
10if sys.platform.count("darwin")>0:
11    import mactime as time
12else:
13    import time
14
15class CalcThread:
16    """Threaded calculation class.  Inherit from here and specialize
17    the compute() method to perform the appropriate operations for the
18    class.
19
20    If you specialize the __init__ method be sure to call
21    CalcThread.__init__, passing it the keyword arguments for
22    yieldtime, worktime, update and complete.
23   
24    When defining the compute() method you need to include code which
25    allows the GUI to run.  They are as follows:
26        self.isquit()          call frequently to check for interrupts
27        self.update(kw=...)    call when the GUI could be updated
28        self.complete(kw=...)  call before exiting compute()
29    The update() and complete() calls accept field=value keyword
30    arguments which are passed to the called function.  complete()
31    should be called before exiting the GUI function.  A KeyboardInterrupt
32    event is triggered if the GUI signals that the computation should
33    be halted.
34
35    The following documentation should be included in the description
36    of the derived class.
37
38    The user of this class will call the following:
39
40        thread = Work(...,kw=...)  prepare the work thread.
41        thread.queue(...,kw=...)   queue a work unit
42        thread.requeue(...,kw=...) replace work unit on the end of queue
43        thread.reset(...,kw=...)   reset the queue to the given work unit
44        thread.stop()              clear the queue and halt
45        thread.interrupt()         halt the current work unit but continue
46        thread.ready(delay=0.)     request an update signal after delay
47        thread.isrunning()         returns true if compute() is running
48
49    Use queue() when all work must be done.  Use requeue() when intermediate
50    work items don't need to be done (e.g., in response to a mouse move
51    event).  Use reset() when the current item doesn't need to be completed
52    before the new event (e.g., in response to a mouse release event).  Use
53    stop() to halt the current and pending computations (e.g., in response to
54    a stop button).
55   
56    The methods queue(), requeue() and reset() are proxies for the compute()
57    method in the subclass.  Look there for a description of the arguments.
58    The compute() method can be called directly to run the computation in
59    the main thread, but it should not be called if isrunning() returns true.
60
61    The constructor accepts additional keywords yieldtime=0.01 and
62    worktime=0.01 which determine the cooperative multitasking
63    behaviour.  Yield time is the duration of the sleep period
64    required to give other processes a chance to run.  Work time
65    is the duration between sleep periods.
66
67    Notifying the GUI thread of work in progress and work complete
68    is done with updatefn=updatefn and completefn=completefn arguments
69    to the constructor.  Details of the parameters to the functions
70    depend on the particular calculation class, but they will all
71    be passed as keyword arguments.  Details of how the functions
72    should be implemented vary from framework to framework.
73
74    For wx, something like the following is needed:
75
76        import wx, wx.lib.newevent
77        (CalcCompleteEvent, EVT_CALC_COMPLETE) = wx.lib.newevent.NewEvent()
78
79        # methods in the main window class of your application
80        def __init__():
81            ...
82            # Prepare the calculation in the GUI thread.
83            self.work = Work(completefn=self.CalcComplete)
84            self.Bind(EVT_CALC_COMPLETE, self.OnCalcComplete)
85            ...
86            # Bind work queue to a menu event.
87            self.Bind(wx.EVT_MENU, self.OnCalcStart, id=idCALCSTART)
88            ...
89
90        def OnCalcStart(self,event):
91            # Start the work thread from the GUI thread.
92            self.work.queue(...work unit parameters...)
93
94        def CalcComplete(self,**kwargs):
95            # Generate CalcComplete event in the calculation thread.
96            # kwargs contains field1, field2, etc. as defined by
97            # the Work thread class.
98            event = CalcCompleteEvent(**kwargs)
99            wx.PostEvent(self, event)
100
101        def OnCalcComplete(self,event):
102            # Process CalcComplete event in GUI thread.
103            # Use values from event.field1, event.field2 etc. as
104            # defined by the Work thread class to show the results.
105            ...
106    """
107
108    def __init__(self,
109                 completefn = None,
110                 updatefn   = None,
111                 yieldtime  = 0.01,
112                 worktime   = 0.01
113                 ):
114        """Prepare the calculator"""
115        self.yieldtime     = yieldtime
116        self.worktime      = worktime
117        self.completefn    = completefn
118        self.updatefn      = updatefn
119        self._interrupting = False
120        self._running      = False
121        self._queue        = []
122        self._lock         = thread.allocate_lock()
123        self._delay        = 1e6
124
125    def queue(self,*args,**kwargs):
126        """Add a work unit to the end of the queue.  See the compute()
127        method for details of the arguments to the work unit."""
128        self._lock.acquire()
129        self._queue.append((args,kwargs))
130        # Cannot do start_new_thread call within the lock
131        self._lock.release()
132        if not self._running:
133            # print "Starting thread"
134            self._time_for_update = time.clock()+1e6
135            thread.start_new_thread(self._run,())
136
137    def requeue(self,*args,**kwargs):
138        """Replace the work unit on the end of the queue.  See the compute()
139        method for details of the arguments to the work unit."""
140        self._lock.acquire()
141        self._queue = self._queue[:-1]
142        self._lock.release()
143        self.queue(*args,**kwargs)
144
145    def reset(self,*args,**kwargs):
146        """Clear the queue and start a new work unit.  See the compute()
147        method for details of the arguments to the work unit."""
148        self.stop()
149        self.queue(*args,**kwargs)
150
151    def stop(self):
152        """Clear the queue and stop the thread.  New items may be
153        queued after stop.  To stop just the current work item, and
154        continue the rest of the queue call the interrupt method"""
155        self._lock.acquire()
156        self._interrupting = True
157        self._queue = []
158        self._lock.release()
159
160    def interrupt(self):
161        """Stop the current work item.  To clear the work queue as
162        well call the stop() method."""
163        self._lock.acquire()
164        self._interrupting = True
165        self._lock.release()
166
167    def isrunning(self): return self._running
168
169    def ready(self, delay=0.):
170        """Ready for another update after delay=t seconds.  Call
171        this for threads which can show intermediate results from
172        long calculations."""
173        self._delay = delay
174        self._lock.acquire()
175        self._time_for_update = time.clock() + delay
176        # print "setting _time_for_update to ",self._time_for_update
177        self._lock.release()
178
179    def isquit(self):
180        """Check for interrupts.  Should be called frequently to
181        provide user responsiveness.  Also yields to other running
182        threads, which is required for good performance on OS X."""
183
184        # Only called from within the running thread so no need to lock
185        if self._running and self.yieldtime>0 and time.clock()>self._time_for_nap:
186            # print "sharing"
187            time.sleep(self.yieldtime)
188            self._time_for_nap = time.clock() + self.worktime
189        if self._interrupting: raise KeyboardInterrupt
190
191    def update(self,**kwargs):
192       
193        """Update GUI with the lastest results from the current work unit."""
194     
195        if self.updatefn != None and time.clock() > self._time_for_update:
196            self._lock.acquire()
197            self._time_for_update = time.clock() + self._delay
198            self._lock.release()
199           
200            #self._time_for_update += 1e6  # No more updates
201           
202            self.updatefn(**kwargs)
203            time.sleep(self.yieldtime)
204            if self._interrupting: raise KeyboardInterrupt
205        else:
206            self.isquit()
207        return
208
209    def complete(self,**kwargs):
210        """Update the GUI with the completed results from a work unit."""
211        if self.completefn != None:
212            self.completefn(**kwargs)
213            time.sleep(self.yieldtime)
214        return
215
216    def compute(self,*args,**kwargs):
217        """Perform a work unit.  The subclass will provide details of
218        the arguments."""
219        raise NotImplemented, "Calculation thread needs compute method"
220
221    def _run(self):
222        """Internal function to manage the thread."""
223        # The code for condition wait in the threading package is
224        # implemented using polling.  I'll accept for now that the
225        # authors of this code are clever enough that polling is
226        # difficult to avoid.  Rather than polling, I will exit the
227        # thread when the queue is empty and start a new thread when
228        # there is more work to be done.
229        while 1:
230            self._lock.acquire()
231            # print "lock aquired"
232            self._time_for_nap = time.clock()+self.worktime
233            self._running = True
234            if self._queue == []: break
235            self._interrupting = False
236            args,kwargs = self._queue[0]
237            self._queue = self._queue[1:]
238            self._lock.release()
239            # print "lock released"
240            try:
241                self.compute(*args,**kwargs)
242            except KeyboardInterrupt:
243                pass
244            except:
245                traceback.print_exc()
246                #print 'CalcThread exception',
247        self._running = False
248
249# ======================================================================
250# Demonstration of calcthread in action
251class CalcDemo(CalcThread):
252    """Example of a calculation thread."""
253    def compute(self,n):
254        total = 0.
255        for i in range(n):
256            #self.update(i=i)
257            for j in range(n):
258                self.isquit()
259                total += j
260        self.complete(total=total)
261
262class CalcCommandline:
263    def __init__(self, n=20000):
264        print thread.get_ident()
265        self.starttime = time.clock()
266        self.done = False
267        self.work = CalcDemo(completefn=self.complete,
268                             updatefn=self.update, yieldtime=0.001)
269        #self.work2 = CalcDemo(completefn=self.complete,
270        #                     updatefn=self.update)
271        #self.work3 = CalcDemo(completefn=self.complete,
272        #                     updatefn=self.update)
273        self.work.queue(n)
274        #self.work2.queue(n)
275        #self.work3.queue(n)
276        print "Expect updates from Main every second and from thread every 2.5 seconds"
277        print ""
278        self.work.ready(.5)
279        while not self.done:
280            time.sleep(1)
281            print "Main thread %d at %.2f"%(thread.get_ident(),time.clock()-self.starttime)
282
283    def update(self,i=0):
284        print "Update i=%d from thread %d at %.2f"%(i,thread.get_ident(),time.clock()-self.starttime)
285        self.work.ready(2.5)
286
287    def complete(self,total=0.0):
288        print "Complete total=%g from thread %d at %.2f"%(total,thread.get_ident(),time.clock()-self.starttime)
289        self.done = True
290
291if __name__ == "__main__":
292    CalcCommandline()
293
294# version
295__id__ = "$Id: calcthread.py 249 2007-06-15 17:03:01Z ziwen $"
296
297# End of file
Note: See TracBrowser for help on using the repository browser.