def __init__(self, *args, **kwargs):
FrameSimpleDelayedBase.__init__(self, *args, **kwargs)
- self.jobID = 1
+ self.jobID = 0
+ self.abortEvent = delayedresult.AbortEvent()
self.Bind(wx.EVT_CLOSE, self.handleClose)
def setLog(self, log):
app, so worker thread continues and sends result to dead frame; normally
your app would exit so this would not happen."""
if self.buttonAbort.IsEnabled():
- self.Hide()
- wx.FutureCall(5000, self.Destroy)
- else:
- self.Destroy()
+ self.log( "Exiting: Aborting job %s" % self.jobID )
+ self.abortEvent.set()
+ self.Destroy()
def handleGet(self, event):
"""Compute result in separate thread, doesn't affect GUI response."""
self.buttonGet.Enable(False)
self.buttonAbort.Enable(True)
-
+ self.abortEvent.clear()
+ self.jobID += 1
+
self.log( "Starting job %s in producer thread: GUI remains responsive"
% self.jobID )
delayedresult.startWorker(self._resultConsumer, self._resultProducer,
- wargs=(self.jobID,), jobID=self.jobID)
+ wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
- def _resultProducer(self, jobID):
+ def _resultProducer(self, jobID, abortEvent):
"""Pretend to be a complex worker function or something that takes
long time to run due to network access etc. GUI will freeze if this
method is not called in separate thread."""
import time
- time.sleep(5)
+ count = 0
+ while not abortEvent() and count < 50:
+ time.sleep(0.1)
+ count += 1
return jobID
def handleAbort(self, event):
- """Abort actually just means 'ignore the result when it gets to
- handler, it is no longer relevant'. We just increase the job ID,
- this will let handler know that the result has been cancelled."""
+ """Abort the result computation."""
self.log( "Aborting result for job %s" % self.jobID )
self.buttonGet.Enable(True)
self.buttonAbort.Enable(False)
- self.jobID += 1
+ self.abortEvent.set()
def _resultConsumer(self, delayedResult):
- # See if we still want the result for last job started
jobID = delayedResult.getJobID()
- if jobID != self.jobID:
- self.log( "Got obsolete result for job %s, ignored" % jobID )
- return
-
- # we do, get result:
+ assert jobID == self.jobID
try:
result = delayedResult.get()
except Exception, exc:
self.log( "Result for job %s raised exception: %s" % (jobID, exc) )
- self.jobID += 1
return
# output result
# get ready for next job:
self.buttonGet.Enable(True)
self.buttonAbort.Enable(False)
- self.jobID += 1
class FrameSimpleDirect(FrameSimpleDelayedBase):
return self.__result
+class AbortedException(Exception):
+ """Raise this in your worker function so that the sender knows
+ not to send a result to handler."""
+ pass
+
+
class Producer(threading.Thread):
"""
Represent the worker thread that produces delayed results.
It causes the given function to run in a separate thread,
and a sender to be used to send the return value of the function.
As with any threading.Thread, instantiate and call start().
+ Note that if the workerFn raises AbortedException, the result is not
+ sent and the thread terminates gracefully.
"""
def __init__(self, sender, workerFn, args=(), kwargs={},
def wrapper():
try:
result = workerFn(*args, **kwargs)
+ except AbortedException:
+ pass
except Exception, exc:
extraInfo = self._extraInfo(exc)
sender.sendException(exc, extraInfo)
return None
+class AbortEvent:
+ """
+ Convenience class that represents a kind of threading.Event that
+ raises AbortedException when called (see the __call__ method, everything
+ else is just to make it look like threading.Event).
+ """
+
+ def __init__(self):
+ self.__ev = threading.Event()
+
+ def __call__(self, timeout=None):
+ """See if event has been set (wait at most timeout if given). If so,
+ raise AbortedException. Otherwise return None. Allows you to do
+ 'while not event():' which will always succeed unless the event
+ has been set (then AbortedException will cause while to exit)."""
+ if timeout:
+ self.__ev.wait(timeout)
+ if self.__ev.isSet():
+ raise AbortedException()
+ return None
+
+ def __getattr__(self, name):
+ """This allows us to be a kind of threading.Event."""
+ if name in ('set','clear','wait','isSet'):
+ return getattr(self.__ev, name)
+
+
def startWorker(
consumer, workerFn,
cargs=(), ckwargs={},
handler = self.__chain[0]
handler( chainTrav )
-