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.
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)
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
callback. Then you
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.