Skip to content

Asyncify - Call Sync Code from Async Code

In many cases for many projects you will need to combine async code with blocking (non async) code.

If you run a function that just blocks and makes Python wait there without using await, it will prevent Python from jumping back and forth between all these concurrent async functions, because it will be just waiting there.

This is called to block the event loop (the event loop is the thing that runs the async functions in turns jumping at the await points).

Let's see how Asyncer's asyncify() can help you with this.

Block the Event Loop

Let's see a broken example first.

Sync Work

Let's see a sync (regular, blocking) function do_sync_work():

# Code above omitted 👆

def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"

# Code below omitted 👇
👀 Full file preview
# 🚨 Don't use this, it will block the event loop! 🚨

import time

import anyio


def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"


async def main():
    message = do_sync_work(name="World")
    print(message)


anyio.run(main)

This function could be talking to a database, a remote API, etc. But it doesn't use await, it just makes Python wait there without a warning using time.sleep(1).

Call Sync Code from Async Code Blocking

Here's the problem.

Let's just call that slow sync (regular, blocking) function directly from inside the async code 😱:

# 🚨 Don't use this, it will block the event loop! 🚨

import time

import anyio


def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"


async def main():
    message = do_sync_work(name="World")
    print(message)


anyio.run(main)

Because that function is not async, but still it makes Python wait there, it will impede any other async code that could have been started from running. It will all have to just wait there doing nothing, wasting computation time. 😭

Use Asyncify

In those cases where you want to run "sync" (synchronous, blocking) code from inside of async code in a way that is compatible with the rest of the async code you can use Asyncer's asyncify(). 🚀

import time

import anyio
from asyncer import asyncify


def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"


async def main():
    message = await asyncify(do_sync_work)(name="World")
    print(message)


anyio.run(main)

asyncify() takes the sync (blocking) function that you want to call and then returns another async function that takes the actual arguments for the original sync function.

Once you call that, Asyncer (using AnyIO) will run that function in a way that doesn't block the event loop.

Then you can await that and get the return value that was safely executed in a "worker thread" without blocking the event loop. 🎉

Notice that now the function do_sync_work is not an async function:

# Code above omitted 👆

def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"

# Code below omitted 👇
👀 Full file preview
import time

import anyio
from asyncer import asyncify


def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"


async def main():
    message = await asyncify(do_sync_work)(name="World")
    print(message)


anyio.run(main)

...it even has a line:

time.sleep(1)

...to simulate a blocking operation (that doesn't use await).

It could be reading a file, talking to a database, etc.

But asyncify() will do the right thing and run it on a worker thread. This way you can mix async code with blocking code more easily.

Typing Support

And of course, because the way Asyncer is designed, you will get typing support with inline errors and autocompletion for the function arguments:

And you will also get typing support for the return value:

And if you used tools like mypy those would also be able to use this typing support to help you ensure your code is correct and prevent many bugs. 😎