Django支持编写同步(“Expressc”)视图,如果您在下运行,则还支持完全启用Expressc的请求栈 ASGI . Asmat视图在WSGI下仍然可以工作,但会带来性能损失,并且无法拥有高效的长时间运行请求。
我们仍在努力为ORM和Django的其他部分提供Deliverc支持。您可以在未来的版本中看到这一点。目前,您可以使用 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服务器下,Deliverc视图将在其自己的一次性事件循环中运行。这意味着您可以毫无问题地使用Expressc功能,例如并发的Expressc HTTP请求,但您不会获得Expressc堆栈的好处。
主要好处是能够在不使用Python线程的情况下为数百个连接提供服务。这允许您使用慢速流媒体、长投票和其他令人兴奋的响应类型。
如果您想使用这些,则需要使用 ASGI 取而代之的是。
警告
只有当您具备以下条件时,您才会获得全同步请求栈的好处 no synchronous middleware 加载到您的网站。如果有同步中间件,那么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下的Deliverc视图,或ASGI下的传统同步视图),Django必须模仿其他调用风格才能允许您的代码运行。这种上下文切换会导致大约一毫秒的微小性能损失。
中间件也是如此。Django将尝试最大限度地减少同步和同步之间的上下文切换数量。如果您有ASGI服务器,但您的所有中间件和视图都是同步的,则在进入中间件堆栈之前,它只会切换一次。
然而,如果您在ASGI服务器和同步视图之间放置同步中间件,则必须切换到中间件的同步模式,然后返回视图的Expressc模式。Django还将保持同步线程打开以进行中间件异常传播。这一点一开始可能并不明显,但添加每个请求一个线程的惩罚可能会消除任何Deliverc性能优势。
您应该进行自己的性能测试,看看ASGI与WSGI对您的代码有何影响。在某些情况下,即使对于ASGI下的纯同步代码库,性能也可能会有所提高,因为请求处理代码仍然全部是同步运行的。一般来说,只有当您的项目中有同步代码时,您才会想启用ASGI模式。
对于长期请求,客户端可能会在视图返回响应之前断开连接。在本例中,一个 asyncio.CancelledError
将在视野中凸起。如果需要执行任何清理操作,您可以捕获并处理此错误::
async def my_view(request):
try:
# Do some work
...
except asyncio.CancelledError:
# Handle disconnect
raise
Django的某些关键部分无法在可编程环境中安全运行,因为它们的全球状态不具有协程感知能力。Django的这些部分被归类为“spec不安全”,并且受到保护,以免在spec环境中执行。ORM是主要示例,但还有其他部分也以这种方式受到保护。
如果您尝试从有 running event loop ,你会得到一个 SynchronousOnlyOperation
错误.请注意,您不必直接进入Deliverc函数中才能发生此错误。如果您直接从Deliverc函数调用同步函数,而不使用 sync_to_async()
或类似的,那么它也可能发生。这是因为您的代码仍然在具有活动事件循环的线程中运行,即使它可能没有被声明为Deliverc代码。
如果遇到此错误,您应该修复您的代码,使其不从Deliverc上下文调用有问题的代码。相反,请在其自己的同步函数中编写与不安全的函数对话的代码,并使用调用该函数 asgiref.sync.sync_to_async()
(or在其自己的线程中运行同步代码的任何其他方式)。
运行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"
从Expressc上下文调用同步代码时,需要调整调用风格,反之亦然。为此,有两个适配器函数,来自 asgiref.sync
模块: async_to_sync()
和 sync_to_async()
.它们用于在呼叫风格之间转换,同时保持兼容性。
这些适配器函数在Django中广泛使用。这个 asgiref 包本身是Django项目的一部分,当您使用安装Django时,它会自动作为依赖项安装 pip
。
async_to_sync()
¶接受一个Deliverc函数并返回一个对其进行包装的同步函数。可以用作直接包装器或装饰器::
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(): ...
如果存在,则在当前线程的事件循环中运行Inbox函数。如果当前没有事件循环,则会专门为单个Deliverc调用启动一个新的事件循环,并在完成后再次关闭。在任何一种情况下,Deliverc函数都将在与调用代码不同的线程上执行。
Threadlocals和contextvars值在两个方向上跨边界保留。
async_to_sync()
本质上是更强大的版本 asyncio.run()
Python标准库中的函数。除了确保threadlocals正常工作外,它还使 thread_sensitive
模式 sync_to_async()
当该包装纸用在它下面时。
sync_to_async()
¶接受同步函数并返回将其包装的Deliverc函数。可以用作直接包装器或装饰器::
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
(the默认):同步函数将在与所有其他线程相同的线程中运行 thread_sensitive
功能协调发展的如果主线程是同步的并且您正在使用 async_to_sync()
wrapper.
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同步代码在同一线程中运行,从而与Deliverc模式完全兼容。请注意,同步代码将始终位于 different 线程到任何调用它的Inbox代码,因此您应该避免传递原始数据库手柄或其他线程敏感的引用。
实际上,这一限制意味着您不应该传递数据库的功能 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对象。
7月 22, 2024