异步支持

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 保护您的数据不受损坏。

装饰者

以下修饰符可以与同步和异步视图函数一起使用:

例如::

from django.views.decorators.cache import never_cache


@never_cache
def my_sync_view(request): ...


@never_cache
async def my_async_view(request): ...

查询和ORM

除了一些例外,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

你也可以 handle client disconnects in streaming responses

同步安全

DJANGO_ALLOW_ASYNC_UNSAFE

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()

async_to_sync(async_function, force_new_loop=False)

接受一个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()

sync_to_async(sync_function, thread_sensitive=True)

接受同步函数并返回将其包装的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对象。