asyncio: recursive event loops

Hello world, I've been recently trying to switch from a library using gevent to a library using asyncio. And do so without contaminating the whole code with the "async" keyword in one go. As expected, calling the synchronous code from a coroutine is pretty straightforward. But the other way around is a bit more tricky. If the function is not running in the thread that runs the event loop, I can use something like this: asyncio.run_coroutine_threadsafe(...).result() Which seems to work, despite the documentation saying that "If the future's result isn't yet available, raises InvalidStateError.". But if the function that want to call a coroutine is in the thread that runs the event loop, then there's not much I can do to run a coroutine in the event loop that indirectly called that function. As a somewhat more concrete example, let's consider a class that sends and receive messages: class Client: async def on_msg(self, msg): process(msg) async def asend(self, msg): print("async sending", msg) return len(msg) def send(self, msg): print("sending", msg) # return await self.asend(msg) async def run(self): await self.on_msg("PING") def process(msg): # Some legacy code that indirectly ends up wanting to call: print("Received", msg) print(client.send("PONG")) client = Client() asyncio.run(client.run()) Unless I missed something, it's pretty difficult to have `Client.send` "await" on `Client.asend` without contaminating the whole code with "async" and "await". I only see two solutions right now. Either have `Client.on_msg` call `process` in a new thread and then use the trick above. But running `process` in a separate thread might have some consequences since it doesn't expect it. Or have `Client.send` start a new event loop in a new thread. Which might behave suprisingly when sharing file descriptors with the existing loop. What I'm proposing is to change a bit the behavior of `loop.run_until_complete` as follow: - If the loop isn't running and there's no loop in the current context, keep the current behavior. - If the loop is running, but not in the current context, use the `call_soon_threadsafe` trick above. - If the loop is running in the current context, then loop until the coroutine is done. Additionally, `asyncio.run` could be changed similarly. There's of course a strong caveat that it's easy to create an infinite recursion with this. But I think it's worth it as it would allow a much easier integration of asyncio into existing code. Which (I think) limit its adoption. What do you think about it? Best regards
participants (1)
-
Celelibi