6.5 Running Functions in the Future
Credit: David Perry
6.5.1 Problem
You want to run a time-consuming
function in a separate thread while allowing the main thread to
continue uninterrupted.
6.5.2 Solution
The Future class sometimes allows you to hide the
fact that you're using threading while still taking
advantage of threading's potential performance
advantages:
from threading import *
import copy
class Future:
def _ _init_ _(self, func, *param):
# constructor
self._ _done = 0
self._ _result = None
self._ _status = 'working'
self._ _C = Condition( ) # Notify on this Condition when result is ready
# Run the actual function in a separate thread
self._ _T = Thread(target=self.Wrapper, args=(func, param))
self._ _T.setName("FutureThread")
self._ _T.start( )
def _ _repr_ _(self):
return '<Future at '+hex(id(self))+':'+self._ _status+'>'
def _ _call_ _(self):
self._ _C.acquire( )
while self._ _done==0:
self._ _C.wait( )
self._ _C.release( )
# Deepcopy _ _result to prevent accidental tampering with it
result = copy.deepcopy(self._ _result)
return result
def isDone(self):
return self._ _done
def Wrapper(self, func, param):
# Run the actual function and housekeep around it
self._ _C.acquire( )
self._ _result = func(*param)
self._ _done=1
self._ _status=`self._ _result`
self._ _C.notify( )
self._ _C.release( )
6.5.3 Discussion
Although Python's thread syntax is nicer than the
syntax in many languages, it can still be a pain if all you want to
do is run a time-consuming function in a separate thread while
allowing the main thread to continue uninterrupted. A
Future object provides a legible and intuitive way
to achieve such an end.
To run a function in a separate thread, simply put it in a
Future object:
>>> A=Future(longRunningFunction, arg1, arg2 ...)
Both the calling thread and the execution of the function will
continue on their merry ways until the caller needs the
function's result. When it does, the caller can read
the result by calling Future like a function. For
example:
>>> print A( )
If the Future object has completed executing, the
call returns immediately. If it is still running, the call (and the
calling thread in it) blocks until the function completes. The result
of the function is stored in an attribute of the
Future instance, so subsequent calls to it return
immediately.
Since you wouldn't expect to be able to change the
result of a function, Future objects are not meant
to be mutable. This is enforced by requiring
Future to be called, rather than directly reading
_ _result. If desired, stronger enforcement of
this rule can be achieved by playing with _ _getattr_
_ and _ _setattr_ _ or, in Python 2.2,
by using property.
Future runs its function only once, no matter how
many times you read it. Thus, you will have to recreate
Future if you want to rerun your function (e.g.,
if the function is sensitive to the time of day).
For example, suppose you have a function named
muchComputation that can take a rather long time
(tens of seconds or more) to compute its results, because it churns
along in your CPU or it must read data from the network or from a
slow database. You are writing a GUI, and a button on that GUI needs
to start a call to muchComputation with suitable
arguments, displaying the results somewhere on the GUI when done. You
can't afford to run the function itself as the
command associated with the button, since if you
did, the whole GUI would appear to freeze until the computation is
finished, and that is unacceptable. Future offers
one easy approach to handling this situation. First, you need to add
a list of pending Future instances that are
initially empty to your application object called, for example,
app.futures. When the button is clicked, execute
something like this:
app.futures.append(Future(muchComputation, with, its, args, here))
and then return, so the GUI keeps being serviced
(Future is now running the function, but in
another thread). Finally, in some periodically executed poll in your
main thread, do something like this:
for future in app.futures[:]: # Copy list and alter it in loop
if future.isDone( ):
appropriately_display_result(future( ))
app.futures.remove(future)
6.5.4 See Also
Documentation of the standard library modules
threading and copy in the
Library Reference; Practical Parallel
Programming, by Gregory V. Wilson (MIT Press, 1995).
|