1

I'm trying to find a solution to call async function in a Synchronous context.

And following is my references:

But I find that, asyncio.get_event_loop() fails when following asyncio.run(), here is my code to reproduce this issue:

import asyncio

async def asyncfunction(n):
    print(f'before sleep in asyncfunction({ n })')
    await asyncio.sleep(1)
    print(f'after sleep in asyncfunction({ n })')
    return f'result of asyncfunction({ n })'

def callback(r):
    print(f'inside callback, got: {r}')

r0 = asyncio.run(asyncfunction(0)) # cause following asyncio.get_event_loop() fail.
callback(r0)
print('sync code following asyncio.run(0)')

r1 = asyncio.run(asyncfunction(1)) # but following asyncio.run() still works.
callback(r1)
print('sync code following asyncio.run(1)')

async def wrapper(n):
    r = await asyncfunction(n)
    callback(r)

asyncio.get_event_loop().create_task(wrapper(2)) #fail if there  is asyncio.run() before
print('sync code following loop.create_task(2)')

#RuntimeError: There is no current event loop in thread 'MainThread'.

asyncio.get_event_loop().create_task(wrapper(3)) #the second call works if there is no asyncio.run() before
print('sync code following loop.create_task(3)')

# main

_all = asyncio.gather(*asyncio.all_tasks(asyncio.get_event_loop()))
asyncio.get_event_loop().run_until_complete(_all)

I think it might because that the event loop is "consumed" by something somehow, and asyncio.set_event_loop(asyncio.new_event_loop()) might be a workaround, but I'm not sure whether that is an expected usage for end-user to set event loop mannually. And I'm also wondering why and how everything happens here.


After read some of the source code of asyncio.run. I can know why this happens.

But I'm still wondering what is the expected way to call async function in a Synchronous context ?

It seems that the following code works (set a new event loop after each asyncio.run() call) :

asyncio.run(asyncfunction()) 
asyncio.set_event_loop(asyncio.new_event_loop())

but that is somehow weird, and doesn't seems to be the expected way.

3
  • According to the docs for asyncio.run: “ This function always creates a new event loop and closes it at the end.” Commented Dec 3, 2021 at 13:12
  • @dirn Thank you for point out that. I added more description and sub question now. Commented Dec 3, 2021 at 13:24
  • See this answer: stackoverflow.com/questions/69710875/… Commented Dec 3, 2021 at 20:24

1 Answer 1

3

When you call asyncio.run it creates a new event loop each time you call it. That event loop is subsequently destroyed when asyncio.run finishes. So in your code example after your second asyncio.run finishes there is no event loop at all, the two you've previously created don't exist anymore. asyncio.get_event_loop will normally create a new event loop for you unless set_event_loop was previously called, which asyncio.run does do (which explains why if you remove asyncio.run things work). To fix your code, you should create a new event loop and just use that instead of calling get_event_loop, bear in mind that this is a third loop and that may not be what you want.

import asyncio

async def asyncfunction(n):
  print(f'before sleep in asyncfunction({ n })')
  await asyncio.sleep(1)
  print(f'after sleep in asyncfunction({ n })')
  return f'result of asyncfunction({ n })'

def callback(r):
  print(f'inside callback, got: {r}')

r0 = asyncio.run(asyncfunction(0))
callback(r0)
print('sync code following asyncio.run(0)')

r1 = asyncio.run(asyncfunction(1))
callback(r1)
print('sync code following asyncio.run(1)')

async def wrapper(n):
  r = await asyncfunction(n)
  callback(r)

loop = asyncio.new_event_loop()
loop.create_task(wrapper(2))
print('sync code following loop.create_task(2)')


loop.create_task(wrapper(3))
print('sync code following loop.create_task(3)')

# main

_all = asyncio.gather(*asyncio.all_tasks(loop))
loop.run_until_complete(_all)
Sign up to request clarification or add additional context in comments.

15 Comments

Thanks for your answer, but could you please point out what is the expected way to call async function in a Synchronous context in python asyncio ?
@luochen1990 it depends on your situation. Normally you would create an event loop manually and call loop.run_until_complete(async_function) but creating a task as you've done works if you need it to return instantly.
So you mean asyncio.new_event_loop().run_until_complete(async_function()) ?
@luochen1990 if you want a new event loop created each time then yes, but that seems unneeded. You can create one event loop with loop = asyncio.new_event_loop() and reuse that across multiple calls with loop.run_until_complete
From stackoverflow.com/a/56662635/1608276 , "run_until_complete is not for running any number of arbitrary async functions synchronously, it is for running the main entry point of your entire async program. This constraint is not immediately apparent from the docs." I'm not sure which one is correct.... :(
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.