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
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/BOQIH5GE4SOJULVX3RVH5FIRCIWHNHZY/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to