pythonpython-3.xpython-asyncio

How to run a coroutine and wait it result from a sync func when the loop is running?


I have a code like the foolowing:

def render():
    loop = asyncio.get_event_loop()

    async def test():
        await asyncio.sleep(2)
        print("hi")
        return 200

    if loop.is_running():
        result = asyncio.ensure_future(test())
    else:
        result = loop.run_until_complete(test())

When the loop is not running, it is quite easy: just use loop.run_until_complete and it returns the coro result but if the loop is already running (my blocking code running in the app which is already running the loop). I cannot use loop.run_until_complete since it will raise an exception; when I call asyncio.ensure_future the task gets scheduled and run, but I want to wait there for the result, does anybody know how to do this? Docs are not very clear on how to do this.

I tried passing a concurrent.futures.Future calling set_result inside the coro and then calling Future.result() on my blocking code, but it doesn't work: it blocks there and does not let anything else run. Any help would be appreciated.


Solution

  • To implement runner with the proposed design, you would need a way to single-step the event loop from a callback running inside it. Asyncio explicitly forbids recursive event loops, so this approach is a dead end.

    Given that constraint, you have two options:

    1. make render() itself a coroutine;
    2. execute render() (and its callers) in a thread different than the thread that runs the asyncio event loop.

    Assuming #1 is out of the question, you can implement the #2 variant of render() like this:

    def render():
        loop = _event_loop  # can't call get_event_loop()
    
        async def test():
            await asyncio.sleep(2)
            print("hi")
            return 200
    
        future = asyncio.run_coroutine_threadsafe(test(), loop)
        result = future.result()
    

    Note that you cannot use asyncio.get_event_loop() in render because the event loop is not (and should not be) set for that thread. Instead, the code that spawns the runner thread must call asyncio.get_event_loop() and send it to the thread, or just leave it in a global variable or a shared structure.