Asynchronous programming

Chris Angelico rosuav at gmail.com
Thu Aug 11 00:41:27 EDT 2016


On Thu, Aug 11, 2016 at 1:53 PM, Steven D'Aprano
<steve+python at pearwood.info> wrote:
> How is this the same as, or different from, event-based programming? I'm
> familiar with programming in an event-based language where the interpreter
> itself provides an event loop and dispatches messages to your objects:
>
> - I define objects such as buttons, text fields, etc., and give them methods
> ("handlers") which handle certain messages such as "mouseUp", etc.;
>
> - the interpreter runs in a loop, firing off messages in response to the
> user's actions;
>
> - there's a message passing hierarchy, whereby messages are first received
> by (let's say) the button the user clicked on, if not handled by a mouseUp
> method it is passed on to the next object in the hierarchy (say, the
> window), and then finally to the interpreter itself, at which point it
> either ignores the message or raises an exception.
>
>
> If I'm using async and await in Python, where's the event loop? What are the
> messages, and where are they sent? Or am I on the wrong track altogether?

There'll be one somewhere, probably at some top-level main loop. Async
programming isn't usually compared with events *as such*, but with
callbacks. Consider these three ways of doing a database transaction:

def synchronous(id):
    trn = conn.begin_transaction()
    trn.execute("select name from people where id=%d", (id,))
    name, = trn.fetchone()
    trn.execute("update people set last_seen=now() where id=%d", (id,))
    trn.commit()
    return name

def callbacks_1(cb, id):
    conn.begin_transaction(callbacks_2, cb, id)
def callbacks_2(trn, cb, id):
    trn.execute("select name from people where id=%d", (id,),
callbacks_3, cb, id)
def callbacks_3(trn, cb, id):
    trn.fetchone(callbacks_4, cb, id)
def callbacks_4(trn, data, cb, id):
    name, = data
    trn.execute("update people set last_seen=now() where id=%d",
(id,), callbacks_5, cb, name)
def callbacks_5(trn, cb, name):
    trn.commit(callbacks_6, cb, name)
def callbacks_6(trn, cb, name):
    cb(name)

def asynchronous(id):
    trn = yield from conn.begin_transaction()
    yield from trn.execute("select name from people where id=%d", (id,))
    name, = yield from trn.fetchone()
    yield from trn.execute("update people set last_seen=now() where
id=%d", (id,))
    yield from trn.commit()
    return name

Change this last one to "async def" and change the "yield from"s into
"await", and you get the new syntax, but it's still built on top of
generators. Basically, async code works by stopping execution in the
middle of a logical operation, and then resuming it later. (The
callbacks example could be improved on some by using closures, and in
JavaScript, where this is really common, you can use anonymous
functions. I still prefer the last form though.) Every time you hit an
"await", you call into that function and see what it says - in this
case, it's going to "yield" somewhere, sending back some sort of
awaitable thing, eg a half-done database operation. When that
operation completes, you go into the next step of the process.

How does this align with event handling? Not very well. Normally event
handling means non-linear execution - for instance, button clicks on a
GUI aren't going to step nicely through a progression. But the two
work well together; if you have a primary event loop that starts out
by looking for GUI events, you could have something like the above
asynchronous function, and it'll give control back to the main loop
while the database is working - thus making the GUI responsive again.
You can mess around with the front end while the back end does its
work.

Hope that helps some.

ChrisA



More information about the Python-list mailing list