The tasks module provides a simple light-weight alternative to threads.
THIS MODULE IS EXPERIMENTAL. Feedback on the API is appreciated. Things may
change in the next few versions, so watch out!
When you have a long-running job you will want to run it in the background,
while the user does other things. There are four ways to do this:
- Use a new thread for each task.
- Use callbacks from an idle handler.
- Use a recursive mainloop.
- Use this module.
Using threads causes a number of problems. Some builds of pygtk/python don't
support them, they introduce race conditions, often lead to many subtle
bugs, and they require lots of resources (you probably wouldn't want 10,000
threads running at once). In particular, two threads can run at exactly the
same time (perhaps on different processors), so you have to be really careful
that they don't both try to update the same variable at the same time. This
requires lots of messy locking, which is hard to get right.
Callbacks work within a single thread. For example, you open a dialog box and
then tell the system to call one function if it's closed, and another if the
user clicks OK, etc. The function that opened the box then returns, and the
system calls one of the given callback functions later. Callbacks only
execute one at a time, so you don't have to worry about race conditions.
However, they are often very awkward to program with, because you have to
save state somewhere and then pass it to the functions when they're called.
A recursive mainloop only works with nested tasks (you can create a
sub-task, but the main task can't continue until the sub-task has
finished). We use these for, eg, rox.alert() boxes since you don't
normally want to do anything else until the box is closed, but it is not
appropriate for long-running jobs.
Tasks use python's generator API to provide a more pleasant interface to
callbacks. See the Task class (below) for more information.
Classes |
| |
- Blocker
-
- IdleBlocker
- TimeoutBlocker
- Task
class Blocker |
|
A Blocker object starts life with 'happened = False'. Tasks can
ask to be suspended until 'happened = True'. The value is changed
by a call to trigger().
Example:
kettle_boiled = tasks.Blocker()
def make_tea():
print "Get cup"
print "Add tea leaves"
yield kettle_boiled;
print "Pour water into cup"
print "Brew..."
yield tasks.TimeoutBlocker(120)
print "Add milk"
print "Ready!"
tasks.Task(make_tea())
# elsewhere, later...
print "Kettle boiled!"
kettle_boiled.trigger()
You can also yield a list of Blockers. Your function will resume
after any one of them is triggered. Use blocker.happened to
find out which one(s). Yielding a Blocker that has already
happened is the same as yielding None (gives any other Tasks a
chance to run, and then continues). |
|
Methods defined here:
- __init__(self)
- add_task(self, task)
- Called by the schedular when a Task yields this
Blocker. If you override this method, be sure to still
call this method with Blocker.add_task(self)!
- trigger(self)
- The event has happened. Note that this cannot be undone;
instead, create a new Blocker to handle the next occurance
of the event.
|
class IdleBlocker(Blocker) |
|
An IdleBlocker blocks until a task starts waiting on it, then
immediately triggers. An instance of this class is used internally
when a Task yields None. |
|
Methods defined here:
- add_task(self, task)
- Also calls trigger.
|
class Task |
|
Create a new Task when you have some long running function to
run in the background, but which needs to do work in 'chunks'.
Example (the first line is needed to enable the 'yield' keyword in
python 2.2):
from __future__ import generators
from rox import tasks
def my_task(start):
for x in range(start, start + 5):
print "x =", x
yield None
tasks.Task(my_task(0))
tasks.Task(my_task(10))
rox.mainloop()
Yielding None gives up control of the processor to another Task,
causing the sequence printed to be interleaved. You can also yield a
Blocker (or a list of Blockers) if you want to wait for some
particular event before resuming (see the Blocker class for details). |
|
Methods defined here:
- __init__(self, iterator, name=None)
- Call iterator.next() from a glib idle function. This function
can yield Blocker() objects to suspend processing while waiting
for events. name is used only for debugging.
|
class TimeoutBlocker(Blocker) |
|
Triggers after a set number of seconds. rox.toplevel_ref/unref
are called to prevent the app quitting while a TimeoutBlocker is
running. |
|
Methods defined here:
- __init__(self, timeout)
- Trigger after 'timeout' seconds (may be a fraction).
| |