16

We can use both functions to run any async function synchronously:

import asyncio
from asgiref.sync import async_to_sync

asyncio.run(asyncio.sleep(1))
async_to_sync(asyncio.sleep)(1)

What is the difference? Can we always use asyncio.run instead of async_to_sync?

Max Malysh
  • 29,384
  • 19
  • 111
  • 115

1 Answers1

13

Differences

  1. They have different purposes. async_to_sync turns an awaitable into a synchronous callable, and asyncio.run executes a coroutine and return the result.

  2. According to documentation, a callable from async_to_sync works in a subthread.

  3. async_to_sync does not create an event loop per-thread in case when you're inside synchronous code which is produced by sync_to_async and running inside asynchronous code. It reuses a loop of asynchronous code. Let's take an example:

import asyncio
from asgiref.sync import async_to_sync, sync_to_async

async def running(n):
    return [await sync_to_async(sync)(i) for i in range(n)]

def sync(n):
    # it will create a new loop for every call
    return asyncio.run(from_sync(n))

async def from_sync(n):
    return n

print("Result:", asyncio.run(running(3)))

This one will run 4 loops: 1 to call running and 3 to call from_sync.

If we use async_to_sync instead of asyncio.run inside sync we will reduce the number of loops to 1 to call running.

To see it you can wrap new_event_loop function:

def print_deco(fn, msg):
    def inner():
        res = fn()
        print(msg, res)
        return res
    return inner
p = asyncio.get_event_loop_policy()
p.new_event_loop = print_deco(p.new_event_loop, "NEW EVENT LOOP:")

You can find a detailed explanation in this post.

alanjds
  • 3,972
  • 2
  • 35
  • 43
Artemij Rodionov
  • 1,721
  • 1
  • 17
  • 22
  • 2
    What will happen if I run async code using `asyncio.run` instead of `async_to_sync`? – Max Malysh Dec 29 '19 at 15:56
  • @MaxMalyshIReinstateMonica As stated in [the documentation](https://docs.djangoproject.com/en/3.0/topics/async/), you are likely to get a [SynchronousOnlyOperation](https://docs.djangoproject.com/en/3.0/ref/exceptions/#django.core.exceptions.SynchronousOnlyOperation) error (since django 3.0) which can be disabled by with `os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"`, but there is also a warning: "If you enable this option and there is parallel access to asynchronous unsafe parts of Django, you may suffer data loss or corruption". – Artemij Rodionov Dec 30 '19 at 00:44
  • This will happen only if we make a _synchronous_ call inside an asynchronous function (e.g. if we will call django ORM inside an event loop). This is why we need to wrap synchronous calls into sync_to_async. My question was about async ---> sync, not the vice versa. – Max Malysh Dec 30 '19 at 07:18
  • @MaxMalyshIReinstateMonica You are right. I have updated the answer – Artemij Rodionov Dec 31 '19 at 04:11