0

I need to call an async function from within a synchronous function.

Can someone educate me on the following: What are the salient differences between sync_fn_a and sync_fn_b, and when would I choose one over the other?

async def my_async_fn(arg1): # internals are not important def sync_fn_a(arg): asyncio.run(my_async_fn(arg)) def sync_fun_b(arg): asyncio.get_event_loop().run_until_complete(my_async_fn(arg)) 
1
  • You should generally use asynchio.run, which is a high level api wrapping asynchio.get_event_loop() and loop.run_until_complete(), while managing the event loop for you. See this question and answer stackoverflow.com/a/62526730/17030540 Commented Nov 6, 2024 at 4:30

2 Answers 2

2

The first thing I should mention is that asyncio.get_event_loop has been deprecated as of version Python 3.12. You should use instead asyncio.new_event_loop as follows:

def sync_fun_b(arg): loop = asyncio.new_event_loop() # Create a new event_loop # Set it as the current event loop so that it will be returned # when asyncio.get_running_loop is called: asyncio.set_event_loop(loop) loop.run_until_complete(my_async_fn(arg)) # Add missing ) 

asynio.run will essentially execute the sequence of operations as shown in sync_fun_b above, i.e. create a new event loop, set the current event loop and run until complete a coroutine. But in addition, after run_until_complete returns, asyncio.run will issue a close method call on the event loop. Consequently every time you call asyncio.run a new event loop has to be created. If you will be calling multiple async coroutines in succession from a "regular" non-async function, then the following would be more efficient than calling asynci.run twice because an event loop is created only once and reused:

import asyncio async def coro1(): ... async def coro2(): ... def main(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: loop.run_until_complete(coro1()) loop.run_until_complete(coro2()) finally: loop.close() if __name__ == '__main__': main() 
Sign up to request clarification or add additional context in comments.

2 Comments

Excellent - thank you for the clarification. Am I right in thinking that each call to asyncio.set_event_loop(loop) should (eventually) be followed by a loop.close() in order to restore any event_loop that was previously in place?
loop.close() does not restore any previous loop that had been set with set_event_loop. If you already have a running loop (you can test this with loop = asyncio.get_running_loop(), which will raise an exception if there is no running loop (and so you need to be be prepared to catch the exception), then there is no point in setting a new event loop for the current thread; the original loop will be returned if you call asyncio.get_running_loop() and you cannot successfully create a new task and await it with the new loop.
1

Under the hood, asyncio.run creates a new event loop, then runs your asynchronous function my_async_fn until it is completed, and then closes the event loop. This is a good option to use when you know that no event loop is already running. If there is already an active event loop when asyncio.run is called then you will raise an error.

asyncio.get_event_loop().run_until_complete on the other hand will either use the existing event loop or create a new event loop and then run my_async_fn until it is completed. This is more useful if you think your code might already by in an event loop e.g. if it is within a Jupyter notebook.

To recap:

asyncio.run is a simpler option and should be used if no event loop is already running, run_until_complete should be used in an already asynchronous environment or if you want to use a pre-existing event loop.

1 Comment

Clear and concise - thank you. I note that asyncio.new_event_loop() appears now to be preferred over asyncio.get_event_loop().

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.