简体   繁体   中英

How to call async function in sync code and break async/await chain (i.e. how to wrap an async function in a sync function)

All my code is written without asyncio in mind; however, I use one function that is async (written by another developer; for my purposes it's a black box). Let's call this func_1 . I need to call this function from within another function, call it func_2 (which itself may be called in an arbitrarily long chain of functions func_3 , func_4 etc...).

Since func_1 is async, I need to await it, but since I call it in func_2 , I need to make func_2 async as well (I can't await within a non-async function). And this goes on an on; I need to turn the entire chain of functions func_2 , func_3 , func_4 into async functions.

Is there a way to avoid this? I just want to call func_1 , wait for it to finish, and use the results in the rest of my normal python code. Can I create a wrapper around func_1 to allow this?

What I want is essentially the following, which doesn't work:

# This is the function defined by someone else
async def func_1(*args):
    return something(*args)

# This is my wrapper
def func_1_wrapper(*args):
    return await func_1(*args)

# So that I can call it like normal within the rest of my code
def func_2(*args):
    # do something
    a = func_1_wrapper(*args)
    # do something else

Essentially, you want to start an event loop (the "engine" of async code), submit your function to the event loop, and then wait until this function is done being executed by the event loop.

One approach is asyncio.run(func_1()) , but you can run into issues if something else in your code already started an event loop or if you are running this in a multithreaded context.

A simple way to handle these edge cases is to use a library like asgiref.sync which allows you to do: func_1_sync = async_to_sync(func_1) and then call func_1_sync() directly from your synchronous function. Here is a snippet from their pypi page:

These [helper functions] allow you to wrap or decorate async or sync functions to call them from the other style (so you can call async functions from a synchronous thread, or vice-versa).

In particular:

AsyncToSync lets a synchronous subthread stop and wait while the async function is called on the main thread's event loop, and then control is returned to the thread when the async function is finished. SyncToAsync lets async code call a synchronous function, which is run in a threadpool and control returned to the async coroutine when the synchronous function completes. The idea is to make it easier to call synchronous APIs from async code and asynchronous APIs from synchronous code so it's easier to transition code from one style to the other.

Another library that does this is syncer :

Sometimes (mainly in test) we need to convert asynchronous functions to normal, synchronous functions and run them synchronously. It can be done by ayncio.get_event_loop().run_until_complete(), but it's quite long…

Syncer makes this conversion easy.

  • Convert coroutine-function (defined by aync def) to normal (synchronous) function
  • Run coroutines synchronously
  • Support both async def and decorator (@asyncio.coroutine) style

If you just want to run it, and the code is already having a loop running in the background, try next(coro.__await__())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM