Search code examples
pythonserverpython-asyncio

Python asyncio loop already running when using asyncio.run and trying to add tasks


I am very new to asyncio and I find the EchoServer example very confusing. I am trying to achieve a simple situation where a server accepts multiple clients, runs that in a coroutine and handles data, and a UI thread which handles ncurses input. I currently have the following, which, in code, conveys the idea I think. But it does not work.

import asyncio

async def do_ui():
    await asyncio.sleep(1)
    print('doing')
    await do_ui()

async def run_game():
    loop = asyncio.get_running_loop()

    server = await GameServer.create_server()
    loop.create_task(server.serve_forever())
    loop.create_task(do_ui())

    loop.run_forever()

def run():
    asyncio.run(run_game())

The problem starts in GameServer.create_server, where I, for encapsulation reasons, want to delegate creating the server to. However this is an asynchronous action (for some reason) and so has to be awaited. See the server code below:

class GameServer:

    @staticmethod
    async def create_server():
        loop = asyncio.get_running_loop()
        return await loop.create_server(
                    lambda: GameServerProtocol(),
                    '127.0.0.1', 8888
        )

This forces me to make run_game async aswell and await it in the run method, which is my setup.py entrypoint, so I can't do that. Using the asyncio.run method however starts a different event loop and I am not able to access it anymore.

How do I solve this? And to vent my frustration, how is this in any way easier than just using threads?


Solution

  • You cannot use loop.run_forever() whilst the event loop is already running. For example, the following code will not work:

    import asyncio
    
    async def main():
        loop=asyncio.get_running_loop()
        loop.run_forever()
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    But this code will work, and has the "run forever" behaviour you appear to be looking for:

    import asyncio
    
    async def do_ui():
        while True:
            await asyncio.sleep(1)
            print('doing ui')
    
    async def main():
        loop = asyncio.get_running_loop()
        loop.create_task(do_ui())
        # insert game server code here
    
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.create_task(main())
        loop.run_forever()