How does sync_to_async convert sync functions to async

Question:

I know that asgiref.sync.sync_to_async is for running sync code in an async context and cannot magically convert them to async code. I have also seen this question and the examples in the answer. But I came up with an unexpected case that I can’t wrap my head around. It seems that sync_to_async is converting time.sleep to an async sleep. Here is the example:
I have two jobs. First job has a loop of 3 with 1-second interval between each iteration. second job just has a wait interval of 3 seconds using time.sleep(). I expect to get the same results no matter how I run the sync wait function (directly or by sync_to_async). But it’s not happening. why is this happening?

import asyncio
import time
from asgiref.sync import sync_to_async


async def first_job():
    for i in range(3):
        print(time.time(), "first start")
        await asyncio.sleep(1)
        print(time.time(), "first finish")


def sync_wait():
    print(time.time(), "wait start")
    time.sleep(3)
    print(time.time(), "wait finish")


async def second_job_sync_wait():
    print(time.time(), "second start")
    sync_wait()
    print(time.time(), "second finish")


async def second_job_async_wait():
    print(time.time(), "second start")
    await sync_to_async(sync_wait)()
    print(time.time(), "second finish")


async def main():
    print("========== sync wait:")
    await asyncio.gather(second_job_sync_wait(), first_job())
    print("========== async wait:")
    await asyncio.gather(second_job_async_wait(), first_job())

asyncio.run(main())

output:

========== sync wait:
1676477101.0311968 second start
1676477101.031214 wait start
1676477104.036779 wait finish
1676477104.0369549 second finish
1676477104.037318 first start (0)
1676477105.0388129 first finish (0)
1676477105.03891 first start (1)
1676477106.040628 first finish (1)
1676477106.041095 first start (2)
1676477107.043103 first finish (2)
========== async wait:
1676477107.043814 second start
1676477107.045408 wait start
1676477107.045689 first start (0)
1676477108.047393 first finish (0)
1676477108.047503 first start (1)
1676477109.048685 first finish (1)
1676477109.048785 first start (2)
1676477110.04971 first finish (2)
1676477110.050215 wait finish
1676477110.0516758 second finish
Asked By: Saeed Mofidi

||

Answers:

It just runs the synchronous function in another thread. The time.sleep doesn’t block your async code with sync_to_async because it’s running in a different thread from your other code.

sync_to_async does more than that – it puts in some extra work to make some unusual guarantees for code that cares about those guarantees – but none of that is relevant here. You can read more in the docs if you want.

Answered By: user2357112