异步支持 — Django 文档

来自菜鸟教程
Django/docs/3.0.x/topics/async
跳转至:导航、​搜索

异步支持

3.0 版中的新功能。


Django 已开发支持异步(“async”)Python,但尚不支持异步视图或中间件; 他们将在未来的版本中推出。

对异步生态系统其他部分的支持有限; 也就是说,Django 可以原生地谈论 ASGI,以及一些异步安全支持。

异步安全

Django 的某些关键部分无法在异步环境中安全运行,因为它们具有无法感知协程的全局状态。 Django 的这些部分被归类为“async-unsafe”,并且在异步环境中不会被执行。 ORM 是主要示例,但还有其他部分也以这种方式受到保护。

如果您尝试从存在 运行事件循环 的线程中运行这些部分中的任何一个,您将收到 SynchronousOnlyOperation 错误。 请注意,您不必直接在异步函数内即可发生此错误。 如果您直接从异步函数调用同步函数,而没有通过 sync_to_async() 或线程池之类的方法,那么它也可能发生,因为您的代码仍在异步上下文中运行。

如果遇到此错误,则应修复代码以不从异步上下文调用有问题的代码; 相反,在自己的同步函数中编写与 async-unsafe 对话的代码,并使用 asgiref.sync.sync_to_async() 或任何其他在其自己的线程中运行同步代码的首选方式来调用它。

如果您 绝对 迫切需要从异步上下文运行此代码 - 例如,它是由外部环境强加给您的,并且您确定它不可能同时运行(例如 您在 Jupyter 笔记本中),那么您可以使用 DJANGO_ALLOW_ASYNC_UNSAFE 环境变量禁用警告。

警告

如果您启用此选项并且并发访问 Django 的异步不安全部分,您可能会遭受数据丢失或损坏。 非常小心,不要在生产环境中使用它。


如果您需要在 Python 中执行此操作,请使用 os.environ 执行此操作:

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

异步适配器函数

从异步上下文调用同步代码时需要调整调用风格,反之亦然。 为此,asgiref.sync 包提供了两个适配器函数:async_to_sync()sync_to_async()。 它们用于在同步和异步调用样式之间转换,同时保持兼容性。

这些适配器函数在 Django 中被广泛使用。 asgiref 包本身是 Django 项目的一部分,当您使用 pip 安装 Django 时,它会自动安装为依赖项。

async_to_sync()

async_to_sync(async_function, force_new_loop=False)

包装一个异步函数并在它的位置返回一个同步函数。 可以用作直接包装器或装饰器:

from asgiref.sync import async_to_sync

sync_function = async_to_sync(async_function)

@async_to_sync
async def async_function(...):
    ...

异步函数在当前线程的事件循环中运行(如果存在)。 如果当前没有事件循环,则专门为异步功能启动一个新的事件循环,并在完成后再次关闭。 在任一情况下,异步函数都将在与调用代码不同的线程上执行。

Threadlocals 和 contextvars 值在两个方向的边界上都被保留。

async_to_sync() 本质上是 Python 标准库中可用的 asyncio.run() 函数的更强大版本。 除了确保 threadlocals 工作外,它还启用 sync_to_async()thread_sensitive 模式,当该包装器在其下方使用时。


sync_to_async()

sync_to_async(sync_function, thread_sensitive=False)

包装一个同步函数并在它的位置返回一个异步(awaitable)函数。 可以用作直接包装器或装饰器:

from asgiref.sync import sync_to_async

async_function = sync_to_async(sync_function)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)

@sync_to_async
def sync_function(...):
    ...

Threadlocals 和 contextvars 值在两个方向的边界上都被保留。

编写同步函数时往往假设它们都在主线程中运行,因此 sync_to_async() 有两种线程模式:

  • thread_sensitive=False(默认):同步函数将在一个全新的线程中运行,一旦完成就会关闭。
  • thread_sensitive=True:同步函数将与所有其他 thread_sensitive 函数在同一线程中运行,这将是主线程,如果主线程是同步的并且您正在使用 async_to_sync () 包装器。

线程敏感模式非常特殊,在同一个线程中运行所有功能需要做很多工作。 但请注意,它 依赖于 async_to_sync() 在堆栈 上方的使用,才能在主线程上正确运行。 如果您使用 asyncio.run()(或其他选项),它将退回到仅在单个共享线程(而不是主线程)中运行线程敏感函数。

在 Django 中需要这样做的原因是许多库,特别是数据库适配器,要求在创建它们的同一个线程中访问它们,并且许多现有的 Django 代码假定它们都在同一个线程中运行(例如 中间件向请求添加内容以供视图稍后使用)。

我们没有引入与此代码的潜在兼容性问题,而是选择添加此模式,以便所有现有的 Django 同步代码在同一线程中运行,从而与异步模式完全兼容。 请注意,同步代码将始终位于与调用它的任何异步代码不同的 线程中,因此您应该避免在您编写的任何新代码中传递原始数据库句柄或其他线程敏感引用。