Trying to wrap my head around futures and coroutines
Phil Connell
pconnell at gmail.com
Wed Jan 15 05:18:32 EST 2014
On Mon, Jan 06, 2014 at 06:56:00PM -0600, Skip Montanaro wrote:
> 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.
This instinct is exactly right -- the point of coroutines and tulip futures is
to liberate you from having to daisy chain callbacks together.
>
> 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.
It strikes me that what you have two linear sequences of 'things to do':
- 'Tasks', started in reaction to some event.
- Cancellations, if a particular task happens to be running.
So, a reasonable design is to have two long-running coroutines, one that
executes your 'tasks' sequentially, and another that executes cancellations.
These are both fed 'things to do' via a couple of queues populated in event
callbacks.
Something like (apologies for typos/non-working code):
cancel_queue = asyncio.Queue()
run_queue = asyncio.Queue()
running_task = None
running_task_name = ""
def do_A():
cancel_queue.put_nowait("B")
run_queue.put_nowait(("A", _do_A()))
def do_B():
cancel_queue.put_nowait("A")
run_queue.put_nowait(("B", _do_B()))
def do_C():
run_queue.put_nowait(("C", _do_C()))
@asyncio.coroutine
def canceller():
while True:
name = yield from cancel_queue.get()
if running_task_name == name:
running_task.cancel()
@asyncio.coroutine
def runner():
while True:
name, coro = yield from run_queue.get()
running_task_name = name
running_task = asyncio.async(coro)
yield from running_task
def main():
...
cancel_task = asyncio.Task(canceller())
run_task = asyncio.Task(runner())
...
Cheers,
Phil
More information about the Python-list
mailing list