异步支持

New in Django 3.0.

Django 已对异步 (“async”) Python 进行了支持,但还没有支持异步视图或中间件;它们可能在未来的版本中支持。

对异步生态系统的其他部分的支持有限;换句话说,Django 原生的使用 ASGI 和一些异步安全的支持。

异步安全

Django 的一些关键部分无法在异步环境里安全运行,因为它们的全局状态不支持协同工作。这些 Django 部分被归类为 “不安全异步”,并且受到保护,无法在异步环境中执行。ORM 是主要的例子,但其他部分也以这种方式受到保护。

如果你试着从具有 运行事件循环 的线程中运行这些部分中的任何一个,你将得到 SynchronousOnlyOperation 错误。注意,你不必在异步函数内部即可得到这个错误。如果你直接从一个异步函数中调用了同步函数,而没有经过类似 sync_to_async() 或线程池之类的操作,那么它也可能会报这个错误,因为你的代码仍然在异步上下文中运行。

如果遇到了这个错误,你应该修改代码以免在异步上下文中调用这个有问题的代码;相反,你可以编写代码在同步函数中与不安全异步交流,使用 asgiref.sync.sync_to_async() 或在它自己线程内部运行任何其他首选方式来调用。

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

警告

如果启用了这个选项,并且同时访问 Django 的异步不安全 (async-unsafe) 部分,你会遇到数据丢失或损坏,所以一定要非常小心,并且不要再生产环境里这样使用。

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

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

包装一个异步函数并且返回一个同步函数。可以用作直接包装器或装饰器:

  1. from asgiref.sync import async_to_sync
  2. sync_function = async_to_sync(async_function)
  3. @async_to_sync
  4. async def async_function(...):
  5. ...

如果存在异步函数,那么它会在当前线程的事件循环中运行。如果没有当前事件循环,则会为异步函数专门启动一个新的事件循环,并且会在它完成后再次关闭。无论哪种情况,异步函数会在调用代码的不同线程上执行。

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)

包装一个同步函数并且返回一个异步函数。可以用作直接包装器或装饰器:

  1. from asgiref.sync import sync_to_async
  2. async_function = sync_to_async(sync_function)
  3. async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
  4. @sync_to_async
  5. def sync_function(...):
  6. ...

Threadlocals 和 contextvars 值在两个方向的边界上都保持不变。

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

  • thread_sensitive=False (默认):同步函数将在一个全新的线程中运行,该线程一旦完成,将会关闭。
  • thread_sensitive=True: 同步函数将与所有其它 thread_sensitive 函数在相同线程里运行,如果主线程是同步的并且你正在使用 async_to_sync() 装饰器,则该同步函数将成为主线程。

Thread-sensitive(线程敏感)模式非常特殊,在同一个线程中运行所有函数需要做很多工作。但是请注意,它依赖于堆栈中它上面的 async_to_sync() 的使用,以便在主线程上正确运行。如果你使用 asyncio.run() (或其他选项代替),它将退回到单独共享线程(但不是主线程)中运行 thread-sensitive 函数。

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

我们没有引入代码潜在的兼容性问题,而是选择了添加这种模式,以便所有现有的 Django 同步代码都在同一个线程中运行,从而完全兼容异步模式。注意,同步代码始终要与调用它的异步代码保持在不同线程中,所以你应该避免在任何你编写的新代码中传递原始数据库句柄(handles)或者其他 thread-sensitive 引用。