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.
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:
render()
itself a coroutine;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.