Pythonic way to run the asyncio loop forever in python 3.10

David Jander david at protonic.nl
Thu Oct 6 10:10:06 EDT 2022


Hello,

It is highly likely this has been discussed before, but I haven't been able to
find the answer to my question, so if anyone knows, please send me to the
right place. Thanks.

I am migrating a lot of python software to 3.10, which has deprecated calling
asyncio.get_event_loop() from outside the loop. While doing so, I have bumped
against some common (I think) use-cases which seem to have no elegant solution
anymore. At least I can't find one that seems "pythonic" or "correct".

Problem: Before 3.10, asyncio.get_event_loop() was a convenient way to manage
the loop singleton. In single-threaded applications, normally you want only a
single instance of an event loop. The easiest way to make sure there was only
one loop ever created, was to call asyncio.get_event_loop(). Since the only
place this is crucially important to get right is _outside_ of the loop
(because once inside the loop all damage is done already anyway), deprecating
this use-case basically seems to destroy the usefulness of that method, and
also many other methods of loop, like loop.run_forever() and
loop.run_until_complete().

Example: Suppose an application that does a bunch of (blocking and CPU
intensive) initialization, while also setting up event handlers, that may call
things like loop.add_reader() or loop.call_soon() in many different places.
Then, after all initialization is done, loop.run_forever() is called to get the
application running. Notice, that there may not even be a single coroutine
involved.

Someone else asked something similar here:

https://stackoverflow.com/questions/65684730/what-is-the-pythonic-way-of-running-an-asyncio-event-loop-forever

Unfortunately all proposed answers look like dirty hacks.

In concrete, how do I do this nicely?

async def main():
    loop = asyncio.get_running_loop()
    ...
    loop.add_reader(...)
    ...
    loop.call_later(...)
    ...
    ... etc...

    # HACK: Now wait forever to keep the loop running
    await asyncio.Future()

if __name__ == "__main__":
    asyncio.run(main())


It can't be seriously the intention that people do these kind of things,
right?

This example application might not even use coroutines at all, and be entirely
event driven. Yet an initial coroutine is now required. No way to run the loop
without one. The worst thing is that all the initialization and blocking code
cannot be easily mixed with loop setup anymore, because loop setup before
starting the loop has essentially been killed by python developers. Please
correct me if I am wrong about any of this. What am I missing here?

Unfortunately the world is now seemingly filled with horrible and sometimes
broken workarounds for this change introduced in 3.10:

https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web.py#L434

https://github.com/tornadoweb/tornado/blob/master/demos/tcpecho/server.py#L36

etc...

Where is the discussion about this, and where are the proposed solutions?

Python 3.11 seems to mitigate this problem by introducing the asyncio.Runner()
context-manager. But for the time being for 3.10, I see no good solution.

Best regards,

-- 
David Jander


More information about the Python-list mailing list