异步支持
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
:
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)
包装一个同步函数并且返回一个异步函数。可以用作直接包装器或装饰器:
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()
装饰器,则该同步函数将成为主线程。
Thread-sensitive(线程敏感)模式非常特殊,在同一个线程中运行所有函数需要做很多工作。但是请注意,它依赖于堆栈中它上面的 async_to_sync()
的使用,以便在主线程上正确运行。如果你使用 asyncio.run()
(或其他选项代替),它将退回到单独共享线程(但不是主线程)中运行 thread-sensitive 函数。
在 Django 中需要这么做的原因是许多库,特别是数据库适配器,要求它们在创建时所在的同一个线程里对其进行访问,并且许多现有的 Django 代码假设它都在同一进程中运行(比如中间件将内容添加到请求中以供视图稍后使用)。
我们没有引入代码潜在的兼容性问题,而是选择了添加这种模式,以便所有现有的 Django 同步代码都在同一个线程中运行,从而完全兼容异步模式。注意,同步代码始终要与调用它的异步代码保持在不同线程中,所以你应该避免在任何你编写的新代码中传递原始数据库句柄(handles)或者其他 thread-sensitive 引用。