I l@ve RuBoard Previous Section Next Section

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).

    I l@ve RuBoard Previous Section Next Section