Django支持编写异步(“async”)视图,如果运行在 ASGI . 异步视图仍然可以在WSGI下工作,但是会带来性能损失,并且不能有高效的长时间运行的请求。
我们仍在为ORM和Django的其他部分提供异步支持。您可以在以后的版本中看到这一点。现在,您可以使用 sync_to_async()
适配器与Django的同步部分交互。您还可以集成一系列异步本机Python库。
任何视图都可以通过使其可调用部分返回协程来声明为异步--通常,这是使用 async def
。对于基于函数的视图,这意味着使用 async def
。对于基于类的视图,这意味着声明HTTP方法处理程序,如 get()
和 post()
AS async def
(不是ITS __init__()
,或 as_view()
)。
备注
Django使用 asgiref.sync.iscoroutinefunction
以测试您的视图是否为异步视图。如果您实现自己的返回协程的方法,请确保使用 asgiref.sync.markcoroutinefunction
因此,此函数返回 True
。
在WSGI服务器下,异步视图将在它们自己的一次性事件循环中运行。这意味着您可以使用异步特性,例如并发异步HTTP请求,而不会出现任何问题,但是您将无法获得异步堆栈的好处。
其主要优点是能够在不使用Python线程的情况下为数百个连接提供服务。这允许您使用慢速流、长轮询和其他令人兴奋的响应类型。
如果您想使用这些,则需要使用 ASGI 相反。
警告
只有当您拥有完全异步的请求堆栈时,您才能从中获益 没有同步中间件 载入你的网站。如果有一个同步中间件,那么Django必须为每个请求使用一个线程来安全地为它模拟一个同步环境。
可以构建中间件来支持 both sync and async 上下文。Django的一些中间件是这样构建的,但不是全部。要查看Django必须适应哪些中间件,您可以打开 django.request
记录器,并查找有关的日志消息 "Asynchronous handler adapted for middleware ..." 。
在ASGI和WSGI模式下,您仍然可以安全地使用异步支持来并发地运行代码,而不是串行地运行代码。这在处理外部api或数据存储时特别方便。
如果您想调用Django中仍然是同步的一部分,则需要将其包装在 sync_to_async()
打电话。例如::
from asgiref.sync import sync_to_async
results = await sync_to_async(sync_function, thread_sensitive=True)(pk=123)
如果您意外地尝试从异步视图调用Django的一个仅同步的部分,您将触发Django的 asynchronous safety protection 保护您的数据不受损坏。
以下修饰符可以与同步和异步视图函数一起使用:
conditional_page()
xframe_options_deny()
xframe_options_sameorigin()
xframe_options_exempt()
例如::
from django.views.decorators.cache import never_cache
@never_cache
def my_sync_view(request):
...
@never_cache
async def my_async_view(request):
...
除了一些例外,Django还可以异步运行ORM查询:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
详细说明可在 异步查询 ,但简而言之:
全 QuerySet
导致发生SQL查询的方法具有 a
-添加了前缀的异步变量。
async for
在所有QuerySet上都支持(包括 values()
和 values_list()
。)
Django还支持一些使用数据库的异步模型方法:
async def make_book(*args, **kwargs):
book = Book(...)
await book.asave(using="secondary")
async def make_book_with_tags(tags, *args, **kwargs):
book = await Book.objects.acreate(...)
await book.tags.aset(tags)
事务尚不能在异步模式下工作。如果您有一段需要事务行为的代码,我们建议您将该代码段编写为单个同步函数并使用 sync_to_async()
。
在与视图不匹配的模式下运行时(例如,WSGI下的异步视图,或ASGI下的传统sync视图),Django必须模拟其他调用样式,以允许代码运行。此上下文切换会导致大约一毫秒的性能损失。
中间件也是如此。Django将尝试最小化sync和async之间的上下文切换数量。如果您有一个ASGI服务器,但是所有的中间件和视图都是同步的,那么在进入中间件堆栈之前,它只会切换一次。
但是,如果您将同步中间件放在ASGI服务器和异步视图之间,则它必须切换到中间件的同步模式,然后再切换到视图的异步模式。Django还将为中间件异常传播打开同步线程。一开始这可能不明显,但添加每个请求一个线程的惩罚可以消除任何异步性能优势。
你应该自己做性能测试,看看ASGI和WSGI对你的代码有什么影响。在某些情况下,甚至对于ASGI下的纯同步代码库,性能也可能会有所提高,因为请求处理代码仍然都是异步运行的。一般来说,如果项目中有异步代码,则只希望启用ASGI模式。
对于长期请求,客户端可能会在视图返回响应之前断开连接。在本例中,一个 asyncio.CancelledError
将在视野中凸起。如果需要执行任何清理操作,您可以捕获并处理此错误::
async def my_view(request):
try:
# Do some work
...
except asyncio.CancelledError:
# Handle disconnect
raise
Django的某些关键部分不能在异步环境中安全地运行,因为它们的全局状态不是协同路由感知的。Django的这些部分被归类为“async-unsafe”,并且在异步环境中受到保护,不会被执行。ORM是主要的示例,但也有其他部分也以这种方式受到保护。
如果你试图从一个有 运行事件循环 ,您将获得 SynchronousOnlyOperation
错误。请注意,您不必直接在异步函数内部发生此错误。如果您直接从异步函数调用了sync函数,而不使用 sync_to_async()
或者类似的,那么它也可能发生。这是因为您的代码仍在具有活动事件循环的线程中运行,即使它可能未声明为异步代码。
如果遇到此错误,应修复代码,使其不从异步上下文调用有问题的代码。相反,编写与异步不安全函数进行对话的代码,即sync函数,并使用 asgiref.sync.sync_to_async()
(或在自己的线程中运行同步代码的任何其他方式)。
运行Django代码的环境可能会强加给您异步上下文。例如, Jupyter 笔记本电脑和 IPython 交互式Shell都透明地提供了活动的事件循环,以便更容易地与异步API交互。
如果您使用的是IPythonShell程序,则可以通过运行以下命令来禁用此事件循环:
%autoawait off
作为IPython提示符下的命令。这将允许您运行同步代码,而无需生成 SynchronousOnlyOperation
错误;但是,您也不能 await
异步接口。要重新打开事件循环,请运行:
%autoawait on
如果您所在的环境不是IPython(或者您无法关闭 autoawait
在IPython中),您是 certain 您的代码不可能同时运行,而您 absolutely 需要从异步上下文运行同步代码,则可以通过设置 DJANGO_ALLOW_ASYNC_UNSAFE
环境变量设置为任何值。
警告
如果启用此选项,并且并发访问Django的异步不安全部分,则可能会导致数据丢失或损坏。请非常小心,不要在生产环境中使用它。
如果需要从Python内部执行此操作,请使用 os.environ
::
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
从异步上下文调用同步代码时,需要调整调用样式,反之亦然。为此,有两个适配器函数,分别来自 asgiref.sync
模块: async_to_sync()
和 sync_to_async()
. 它们用于在调用样式之间转换,同时保持兼容性。
这些适配器函数在Django中广泛使用。这个 asgiref 包本身是Django项目的一部分,当您使用安装Django时,它会自动作为依赖项安装 pip
。
async_to_sync()
¶接受一个异步函数并返回一个封装它的sync函数。可以用作直接包装器或装饰器:
from asgiref.sync import async_to_sync
async def get_data():
...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data():
...
异步函数在当前线程的事件循环中运行(如果存在)。如果当前没有事件循环,则会为单个异步调用专门启动一个新的事件循环,并在完成后再次关闭。在这两种情况下,异步函数将在调用代码的不同线程上执行。
Threadlocals和contextvars值在两个方向上跨边界保存。
async_to_sync()
本质上是一个更强大的版本 asyncio.run()
函数在Python的标准库中。除了确保threadlocal可以工作外,它还支持 thread_sensitive
模式 sync_to_async()
当包装纸在下面使用时。
sync_to_async()
¶获取一个sync函数并返回一个封装它的异步函数。可以用作直接包装器或装饰器:
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
@sync_to_async
def sync_function():
...
Threadlocals和contextvars值在两个方向上跨边界保存。
同步函数的编写往往假设它们都在主线程中运行,因此 sync_to_async()
有两种线程模式:
thread_sensitive=True
(默认值):同步函数将在与所有其他函数相同的线程中运行 thread_sensitive
功能。如果主线程是同步的,并且您正在使用 async_to_sync()
包装纸。
thread_sensitive=False
:同步函数将在一个全新的线程中运行,一旦调用完成,该线程就会关闭。
警告
asgiref
版本3.3.0更改了 thread_sensitive
参数设置为 True
。这是一个更安全的缺省值,在很多情况下与Django交互的值都是正确的,但是一定要评估 sync_to_async()
如果更新 asgiref
来自以前的版本。
Thread-sensitive mode is quite special, and does a lot of work to run all
functions in the same thread. Note, though, that it relies on usage of
async_to_sync()
above it in the stack to correctly run things on the
main thread. If you use asyncio.run()
or similar, it will fall back to
running thread-sensitive functions in a single, shared thread, but this will
not be the main thread.
在Django中需要这样做的原因是许多库,特别是数据库适配器,都要求在创建它们的同一线程中访问它们。另外,许多现有的Django代码都假设它们都在同一个线程中运行,例如,中间件向请求中添加一些内容,以便以后在视图中使用。
我们没有引入与此代码潜在的兼容性问题,而是选择添加此模式,以便所有现有的Django sync代码在同一线程中运行,从而与异步模式完全兼容。请注意,同步代码将始终位于 不同的 线程指向调用它的任何异步代码,因此应避免传递原始数据库句柄或其他线程敏感引用。
实际上,这一限制意味着您不应该传递数据库的功能 connection
对象时调用 sync_to_async()
。这样做将触发线程安全检查:
# DJANGO_SETTINGS_MODULE=settings.py python -m asyncio
>>> import asyncio
>>> from asgiref.sync import sync_to_async
>>> from django.db import connection
>>> # In an async context so you cannot use the database directly:
>>> connection.cursor()
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from
an async context - use a thread or sync_to_async.
>>> # Nor can you pass resolved connection attributes across threads:
>>> await sync_to_async(connection.cursor)()
django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread
can only be used in that same thread. The object with alias 'default' was
created in thread id 4371465600 and this is thread id 6131478528.
相反,您应该将所有数据库访问封装在可以使用 sync_to_async()
而不依赖于调用代码中的Connection对象。
12月 18, 2023