Add support for aborting the worker thread

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41354 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2006-09-21 19:14:02 +00:00
parent dd267523d0
commit 45c0ea45ff
2 changed files with 54 additions and 22 deletions

View File

@@ -56,7 +56,8 @@ class FrameSimpleDelayed(FrameSimpleDelayedBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
FrameSimpleDelayedBase.__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) self.Bind(wx.EVT_CLOSE, self.handleClose)
def setLog(self, log): def setLog(self, log):
@@ -67,54 +68,50 @@ class FrameSimpleDelayed(FrameSimpleDelayedBase):
app, so worker thread continues and sends result to dead frame; normally app, so worker thread continues and sends result to dead frame; normally
your app would exit so this would not happen.""" your app would exit so this would not happen."""
if self.buttonAbort.IsEnabled(): if self.buttonAbort.IsEnabled():
self.Hide() self.log( "Exiting: Aborting job %s" % self.jobID )
wx.FutureCall(5000, self.Destroy) self.abortEvent.set()
else:
self.Destroy() self.Destroy()
def handleGet(self, event): def handleGet(self, event):
"""Compute result in separate thread, doesn't affect GUI response.""" """Compute result in separate thread, doesn't affect GUI response."""
self.buttonGet.Enable(False) self.buttonGet.Enable(False)
self.buttonAbort.Enable(True) self.buttonAbort.Enable(True)
self.abortEvent.clear()
self.jobID += 1
self.log( "Starting job %s in producer thread: GUI remains responsive" self.log( "Starting job %s in producer thread: GUI remains responsive"
% self.jobID ) % self.jobID )
delayedresult.startWorker(self._resultConsumer, self._resultProducer, 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 """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 long time to run due to network access etc. GUI will freeze if this
method is not called in separate thread.""" method is not called in separate thread."""
import time import time
time.sleep(5) count = 0
while not abortEvent() and count < 50:
time.sleep(0.1)
count += 1
return jobID return jobID
def handleAbort(self, event): def handleAbort(self, event):
"""Abort actually just means 'ignore the result when it gets to """Abort the result computation."""
handler, it is no longer relevant'. We just increase the job ID,
this will let handler know that the result has been cancelled."""
self.log( "Aborting result for job %s" % self.jobID ) self.log( "Aborting result for job %s" % self.jobID )
self.buttonGet.Enable(True) self.buttonGet.Enable(True)
self.buttonAbort.Enable(False) self.buttonAbort.Enable(False)
self.jobID += 1 self.abortEvent.set()
def _resultConsumer(self, delayedResult): def _resultConsumer(self, delayedResult):
# See if we still want the result for last job started
jobID = delayedResult.getJobID() jobID = delayedResult.getJobID()
if jobID != self.jobID: assert jobID == self.jobID
self.log( "Got obsolete result for job %s, ignored" % jobID )
return
# we do, get result:
try: try:
result = delayedResult.get() result = delayedResult.get()
except Exception, exc: except Exception, exc:
self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) self.log( "Result for job %s raised exception: %s" % (jobID, exc) )
self.jobID += 1
return return
# output result # output result
@@ -124,7 +121,6 @@ class FrameSimpleDelayed(FrameSimpleDelayedBase):
# get ready for next job: # get ready for next job:
self.buttonGet.Enable(True) self.buttonGet.Enable(True)
self.buttonAbort.Enable(False) self.buttonAbort.Enable(False)
self.jobID += 1
class FrameSimpleDirect(FrameSimpleDelayedBase): class FrameSimpleDirect(FrameSimpleDelayedBase):

View File

@@ -227,12 +227,20 @@ class DelayedResult:
return self.__result 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): class Producer(threading.Thread):
""" """
Represent the worker thread that produces delayed results. Represent the worker thread that produces delayed results.
It causes the given function to run in a separate thread, 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. and a sender to be used to send the return value of the function.
As with any threading.Thread, instantiate and call start(). 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 __init__(self, sender, workerFn, args=(), kwargs={},
@@ -250,6 +258,8 @@ class Producer(threading.Thread):
def wrapper(): def wrapper():
try: try:
result = workerFn(*args, **kwargs) result = workerFn(*args, **kwargs)
except AbortedException:
pass
except Exception, exc: except Exception, exc:
extraInfo = self._extraInfo(exc) extraInfo = self._extraInfo(exc)
sender.sendException(exc, extraInfo) sender.sendException(exc, extraInfo)
@@ -268,6 +278,33 @@ class Producer(threading.Thread):
return None 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( def startWorker(
consumer, workerFn, consumer, workerFn,
cargs=(), ckwargs={}, cargs=(), ckwargs={},
@@ -373,4 +410,3 @@ class PreProcessChain:
handler = self.__chain[0] handler = self.__chain[0]
handler( chainTrav ) handler( chainTrav )