Trying to wrap my head around futures and coroutines


S

Skip Montanaro

I've been programming for a long while in an event&callback-driven world.
While I am comfortable enough with the mechanisms available (almost 100% of
what I do is in a PyGTK world with its signal mechanism), it's never been
all that satisfying, breaking up my calculations into various pieces, and
thus having my algorithm scattered all over the place.

So, I'm looking for a little guidance. It seems to me that futures,
coroutines, and/or the new Tulip/asyncio package might be my salvation, but
I'm having a bit of trouble seeing exactly how that would work. Let me
outline a simple hypothetical calculation. I'm looking for ways in which
these new facilities might improve the structure of my code.

Let's say I have a dead simple GUI with two buttons labeled, "Do A" and "Do
B". Each corresponds to executing a particular activity, A or B, which take
some non-zero amount of time to complete (as perceived by the user) or
cancel (as perceived by the state of the running system - not safe to run A
until B is complete/canceled, and vice versa). The user, being the fickle
sort that he is, might change his mind while A is running, and decide to
execute B instead. (The roles can also be reversed.) If s/he wants to run
task A, task B must be canceled or allowed to complete before A can be
started. Logically, the code looks something like (I fear Gmail is going to
destroy my indentation):

def do_A():
when B is complete, _do_A()
cancel_B()

def do_B():
when A is complete, _do_B()
cancel_A()

def _do_A():
do the real A work here, we are guaranteed B is no longer running

def _do_B():
do the real B work here, we are guaranteed A is no longer running

cancel_A and cancel_B might be no-ops, in which case they need to start up
the other calculation immediately, if one is pending.

This is pretty simple execution, and if my job was this simple, I'd
probably just keep doing things the way I do now, which is basically to
catch a "complete" or "canceled" signal from the A and B tasks and execute
the opposite task if it's pending. But it's not this simple. In reality
there are lots of, "oh, you want to do X? You need to make sure A, B, and C
are not active." And other stuff like that.

I have this notion that I should be able to write do_A() something like
this:

def do_A():
cancel_B()
yield from ... ???
_do_A()
....

or

def do_A():
future = cancel_B()
future.on_completion(_do_A)
.... or ???

with the obvious similar structure for do_B. To my mind's eye, the first
option is preferable, since it's obvious that when control reaches the line
after the yield from statement, it's fine to do the guts of task A.

So, is my simpleminded view of the world a possibility with the current
facilities available in 3.3 or 3.4?

Thx,

Skip
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top