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 -- https://mail.python.org/mailman/listinfo/python-list