# Django 5.2.8 documentation

# 异步支持

Django 支持编写异步（“async”）视图，如果在 ASGI 下运行，还支持完全异步的请求堆栈。异步视图仍然可以在 WSGI 下运行，但会有性能损失，并且不能有高效的长时间运行的请求。

我们仍然在为 ORM 和 Django 的其他部分提供异步支持。你可以期待在未来的版本中看到这个功能。目前，你可以使用 `sync_to_async()` 适配器来和 Django 的同步部分进行交互。你还可以集成一系列的原生异步 Python 库。

## 异步视图

任何视图都可以通过使其可调用部分返回协程来声明为异步 - 通常情况下，可以使用 `async def` 来实现这一点。对于基于函数的视图，这意味着要使用 `async def` 来声明整个视图。对于基于类的视图，这意味着要将 HTTP 方法处理程序，如 `get()` 和 `post()` 声明为 `async def` （而不是 `__init__()` 或 `as_view()`）。

Note

Django 使用 `asgiref.sync.iscoroutinefunction` 来测试你的视图是否是异步的。如果你实现了自己的协程返回方法，请确保使用 `asgiref.sync.markcoroutinefunction`，这样这个函数会返回 `True`。

在一个 WSGI 服务器下，异步视图将在它们自己的一次性事件循环中运行。这意味着你可以使用异步功能，比如并发的异步 HTTP 请求，而不会出现任何问题，但你不会得到异步堆栈的好处。

主要的好处是能够在不使用 Python 线程的情况下处理数百个连接。这使你能够使用慢速流式传输、长轮询和其他令人兴奋的响应类型。

如果你想使用这些特性，需要使用 ASGI 来部署 Django。

Warning

只有在你的网站中 没有 加载任何同步中间件的情况下，你才会获得完全异步请求堆栈的好处。如果存在同步中间件的话，那么 Django 必须使用线程来为其安全模拟同步环境。

中间件可以构建以支持 同步和异步 上下文。Django 的一些中间件是这样构建的，但不是所有的中间件都是这样的。要查看 Django 需要适应哪些中间件，你可以为 `django.request` 记录器打开调试日志，查找关于 "异步处理程序适应中间件..." 的日志消息。

在 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 的 异步安全保护，以保护你的数据免受损坏。

### 装饰器

以下装饰器可以用于同步和异步视图函数：

- `cache_control()`

- `never_cache()`

- `no_append_slash()`

- `csp_override()`

- `csp_report_only_override()`

- `csrf_exempt()`

- `csrf_protect()`

- `ensure_csrf_cookie()`

- `requires_csrf_token()`

- `sensitive_variables()`

- `sensitive_post_parameters()`

- `gzip_page()`

- `condition()`

- `conditional_page()`

- `etag()`

- `last_modified()`

- `require_http_methods()`

- `require_GET()`

- `require_POST()`

- `require_safe()`

- `vary_on_cookie()`

- `vary_on_headers()`

- `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): ...

### 查询与 ORM

除了一些例外情况，Django 也可以异步运行 ORM 查询：

    async for author in Author.objects.filter(name__startswith="A"):
        book = await author.books.afirst()

详细的说明可以在 异步查询 中找到，简而言之：

- 所有引发 SQL 查询的 `QuerySet` 方法都有一个以 `a` 为前缀的异步变体。

- `async for` 在所有的查询集上都得到支持（包括 `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()` 调用它。

Persistent database connections, set via the `CONN_MAX_AGE` setting, should also be disabled in async mode. Instead, use your database backend's built-in connection pooling if available, or investigate a third-party connection pooling option if required.

### 性能

在与视图不匹配的模式里运行时（比如在 WSGI 下的异步视图，在 ASGI 下的传统同步视图），Django 必须模拟其他调用方式来运行你的代码。这个上下文切换回导致大约 1 毫秒的小性能损失。

这对于中间件也是适用的。Django 将尝试最小化在同步和异步之间的上下文切换次数。如果你使用的是 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_ALLOW_ASYNC_UNSAFE

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

如果你试着从有运行事件循环的线程中运行这部分中的任何一个，你会得到一个 `SynchronousOnlyOperation` 错误。注意，不用在异步函数内部就会得到这个错误。如果你从异步函数中调用一个同步函数，而没有使用 `sync_to_async()` 或类似方法，也会出现这个问题。这是因为你的代码仍然在具有活动事件循环的线程中运行，即使它可能没有被声明为异步代码。

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

在运行你的 Django 代码环境中你可以使用异步上下文语境。例如， Jupyter 笔记本和 IPython 互动环境都是明显地提供了一种激活事件循环，所以与异步 APIs 互动更容易。

如果你正在使用 IPython shell，你可以通过运行以下命令来禁用这个事件循环：

    %autoawait off

作为 IPython 提示符下的命令。这将允许你运行同步代码，而不会生成 `SynchronousOnlyOperation` 错误；但是，你也无法 `await` 异步 API。要重新启用事件循环，请运行：

    %autoawait on

如果你在除了 IPython 之外的环境中（或者因某些原因无法在 IPython 中关闭 `autoawait`），并且你可以 确定 代码不会同时运行，而且你 绝对 需要从异步上下文中运行同步代码，那么您可以通过将 `DJANGO_ALLOW_ASYNC_UNSAFE` 环境变量设置为任何值来禁用警告。

Warning

如果启用此选项并且对 Django 的异步不安全部分进行并发访问，可能会导致数据丢失或损坏。请非常小心，不要在生产环境中使用此选项。

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

    import os

    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

    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()` is essentially a more powerful version of the `asyncio.run()` function in Python's standard library. As well as ensuring threadlocals work, it also enables the `thread_sensitive` mode of `sync_to_async()` when that wrapper is used below it.

### `sync_to_async()`

sync_to_async(sync_function, thread_sensitive=True)

使用同步函数并返回包装它的异步函数。可用作直接包装器或装饰器：

    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`：同步函数将在一个全新的线程中运行，该线程一旦完成，将会关闭。

Warning

`asgiref` 3.3.0版本将 `thread_sensitive` 的默认值改为了 `True` 。这是一个更加安全的默认项，并且在许多情况下与Django交互能得到正确的值。但是在使用 `asgiref` 的旧版本在升级前请评估 `sync_to_async()` 的使用情况。

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

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

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

在实际应用中，这意味着在调用 `sync_to_async()` 时，你不应该传递数据库 `connection` 对象的特性。这样做将触发线程安全检查：

    # 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()` 调用，而不依赖于调用代码中的连接对象。