@ wrote... (6 months, 2 weeks ago)

I thought I understood async programming as I'd written a few non-trivial apps in the past (using Boost ASIO)

Who has two thumbs and suffers from async programming Dunning-Kruger effect? This guy!

So here's the first of hopefully many recipes to make async programming under Python 3.6 a little easier.

Long story short was that I wanted to convert my auto git puller into something a little more robust and manageable and I chose to use Autobahn and Crossbar.io.

It didn't take too long for me to realize that I was going to have a bad time. It also didn't help that I seemed to hit every bug under the sun…

Anyhow, partly due to my lack of understanding of how to properly architect my app and being stubborn on how I wanted my app to look, one of the first major hurdles I hit was trying to wait for an autobahn ApplicationSession (later converted to a Component) to get connected to crossbar so I could make an RPC call.

In non-working pseudo-python this is what I wanted

comp = Component(...)
await comp.connect()                        # this is the hard part
answer = await comp.call('some.function')
print(answer)

but since Component doesn't have a connect() method this proved tricky.

Thankfully I'm friends with crossbar developer Mike and he got me sorted.

A few key points that really helped me were:

  • you can convert a callback to a future
  • instead of decorators you can use component.on('join', callback) (future post)
  • you can add multiple callbacks to a component event (future post)

Basically, using the powers of closures, you set a Future during the join event callback. Then you await that Future until the callback happens.

import asyncio
from autobahn.asyncio.component import Component

async def main(opts):

    comp = Component(
        transports='ws://localhost:8080',
        realm='demorealm',
    )

    f_session = asyncio.Future()

    @comp.on_join
    def joined(session, details):
        # we're checking session.done() in case this is the second time
        # this is run, ie: in the case of a reconnect
        if not f_session.done():
            f_session.set_result(session)

    comp.start()
    session = await f_session

    answer = await comp._session.call('some.function')
    print(answer)

if __name__ == '__main__':

    loop = asyncio.get_event_loop()
    loop.run_until_complete( main(opts) )
    loop.close()

the main parts of all that are:

comp = Component(...)
f_session = asyncio.Future()        # 1 - create a future

@comp.on_join
def joined(session, details):
    f_session.set_result(session)   # 4 - set future result

comp.start()                        # 2 - start the connection process
session = await f_session           # 3 - await the future
# 5 we now have a session

So this final version (sans boilerplate) looks a lot like my pseudo-python that I wanted! Happy times all around.

Now that I've documented this, the next entry in this “series” will be on how to get the same result without needing to wait for the session to be ready.

Category: tech, Tags: async, python
Comments: 0
Click here to add a comment