Django — async_to_sync 与 asyncio.run

Posted

技术标签:

【中文标题】Django — async_to_sync 与 asyncio.run【英文标题】:Django — async_to_sync vs asyncio.run 【发布时间】:2019-12-27 17:02:05 【问题描述】:

我们可以使用这两个函数来同步运行任何异步函数:

import asyncio
from asgiref.sync import async_to_sync

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

有什么区别?我们可以总是使用asyncio.run 代替async_to_sync 吗?

【问题讨论】:

【参考方案1】:

区别

    他们有不同的目的。 async_to_sync 将 awaitable 转换为同步可调用对象,asyncio.run 执行协程并返回结果。

    根据documentation,来自async_to_sync 的可调用对象在子线程中工作。

    async_to_sync 不会为每个线程创建事件循环,以防您在由sync_to_async 生成并在异步代码中运行的同步代码中。它重用了一个异步代码循环。举个例子:

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)))

这将运行 4 个循环:1 次调用 running,3 次调用 from_sync

如果我们在sync 中使用async_to_sync 而不是asyncio.run,我们会将循环数减少到1 以调用running

要查看它,您可以包装 new_event_loop 函数:

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:")

你可以在这个post找到详细的解释。

【讨论】:

如果我使用asyncio.run而不是async_to_sync运行异步代码会发生什么? @MaxMalyshIReinstateMonica 如the documentation 中所述,您可能会收到SynchronousOnlyOperation 错误(从django 3.0 开始),可以使用os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" 禁用它,但也有警告:“如果您启用此选项并且可以并行访问 Django 的异步不安全部分,您​​可能会遭受数据丢失或损坏”。 只有当我们在异步函数中进行 同步 调用时才会发生这种情况(例如,如果我们将在事件循环中调用 django ORM)。这就是为什么我们需要将同步调用包装到 sync_to_async 中。我的问题是关于异步 ---> 同步,反之亦然。 @MaxMalyshIReinstateMonica 你是对的。我已经更新了答案

以上是关于Django — async_to_sync 与 asyncio.run的主要内容,如果未能解决你的问题,请参考以下文章

Django 3.0 — 异步测试后数据库连接未关闭

Django Channels 是不是会在每次使用时创建一个新的消费者?

Django之路:安装与配置

Django入门进阶与项目实战

Django RESTful API - django-piston 与 django-tastypie

将现有的 django 应用程序与 django-cms 集成