---
title: "异步支持"
version: 4.0
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.0/topics/async/
canonical: https://djangodocs.dev/zh-hans/4.0/topics/async/
---
# 异步支持

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

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

## 异步视图

任何视图可以通过使它的可调用部分返回一个协程来声明为异步——通常，这是使用 `async def` 完成的。对于基于函数的视图，需要使用 `async def` 来声明所有视图。对于基于类的视图，需要将它的 `__call__()` 方法作为 `async def` （而不是 `__init__()` 或 `as_view()` ）。

> **Note**
>
> Django 使用 `asyncio.iscoroutinefunction` 来测试视图是否为异步。如果你实现了自己的方法来返回协同程序，请确保你把视图的 `_is_coroutine` 属性设置为 `asyncio.coroutines._is_coroutine` ，这样函数将返回 `True`。

WSGI 服务器下，异步视图将在其自有的一次性事件循环中运行。这意味着你可以放心使用异步特性（例如并发异步 HTTP 请求），但是你不会获得异步堆栈的好处。

主要优点是无需使用 Python 线程就能服务数百个连接。这就允许你使用慢流（slow streaming）、长轮询和其他响应类型。

如果你想使用这些特性，需要使用 [ASGI](/zh-hans/4.0/howto/deployment/asgi/) 来部署 Django。

> **Warning**
>
> 如果你的站点中没有 *非同步中间件*，那么你将得到完全异步请求栈的好处。如果有一个同步中间件，那么 Django 必须在每个请求中使用一个线程来安全地为它模拟一个同步环境。
>
> 可以构建中间件来支持 [同步和异步](/zh-hans/4.0/topics/http/middleware/#async-middleware) 上下文。一些 Django 中间件是这么构建的，但不是所有都这样。要查看 Django 能够支持哪些中间件，你可以为 `django.request` 记录器打开调试日志，而且要查看有关 *"Synchronous middleware ... adapted"* 的日志消息。

在 ASGI 和 WSGI 模式里，你可以始终安全地使用异步支持来并发运行代码而不是串行。这在处理外部 API 或数据存储时特别方便。

如果你想调用仍处于同步的 Django 部分（比如 ORM），则需要用 [`sync_to_async()`](#asgiref.sync.sync_to_async) 调用来包装它。例如：

```
from asgiref.sync import sync_to_async

results = await sync_to_async(Blog.objects.get, thread_sensitive=True)(pk=123)
```

你可能发现，移动任何 ORM 代码到它自己的函数中并使用 [`sync_to_async()`](#asgiref.sync.sync_to_async) 来调用整个函数会更容易。例如：

```
from asgiref.sync import sync_to_async

def _get_blog(pk):
    return Blog.objects.select_related('author').get(pk=pk)

get_blog = sync_to_async(_get_blog, thread_sensitive=True)
```

如果你不小心从异步视图中调用一个仍然处于同步状态的 Django 部分，那么你将触发 Django 的 [异步安全保护](#async-safety) 来保护你的数据不被破坏。

### 性能

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

这对中间件也是如此。Django 将尝试最小化同步和异步之间上下文切换的次数。如果你有一个 ASGI 服务器，但所有中间件和视图是同步的，那么在进入中间件堆栈之前，它将仅切换一次。

但是，如果你把同步的中间件放在 ASGI 服务器和异步的视图之间，就必须为中间件切换到同步模式，然后再回到视图的异步模式。Django 还将保持同步线程的开放，以便中间件的异常传播。这可能在一开始并不明显，但增加每个请求一个线程的惩罚可以消除任何异步性能的优势。

你应该执行性能测试来观察 ASGI 和 WSGI 对你的代码有什么影响。在一些案例中，即使对于 ASGI 下的纯同步代码库，性能也可能会有所提高，因为请求处理代码仍然全部异步执行。通常，只有当项目有异步代码时，才需要开启 ASGI 模式。

## 异步安全

#### `DJANGO_ALLOW_ASYNC_UNSAFE`

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

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

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

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

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

```
%autoawait off
```

作为IPython终端的命令。这会使你得以运行同步代码而不产生:exc:~django.core.exceptions.SynchronousOnlyOperation 错误。然而，你无法等候异步API。重新打开事件循环，请运行:

```
%autoawait on
```

If you're in an environment other than IPython (or you can't turn off
`autoawait` in IPython for some reason), you are *certain* there is no chance
of your code being run concurrently, and you *absolutely* need to run your sync
code from an async context, then you can disable the warning by setting the
[`DJANGO_ALLOW_ASYNC_UNSAFE`](#envvar-DJANGO_ALLOW_ASYNC_UNSAFE) environment variable to any value.

> **Warning**
>
> 如果启用了这个选项，并且同时访问 Django 的异步不安全 (async-unsafe) 部分，你会遇到数据丢失或损坏，所以一定要非常小心，并且不要再生产环境里这样使用。

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

```
import os

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
```

## 异步适配函数

当从异步的上下文中调用同步的代码时，有必要适配调用风格，反之亦然。为此，有两个适配器功能，可从 `asgiref.sync` 模块中获取：[`async_to_sync()`](#asgiref.sync.async_to_sync) 和 [`sync_to_async()`](#asgiref.sync.sync_to_async) 。它们用于调用样式之间转换，同时保持兼容性。

这些适配函数广泛应用于 Django。[asgiref](https://pypi.org/project/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()`](#asgiref.sync.async_to_sync) 本质上是 Python 标准库中 [`asyncio.run()`](https://docs.python.org/3/library/asyncio-runner.html#asyncio.run) 函数更强大的版本。在确保 threadlocals 工作之外，当在它下面使用包装时，它也会启用 [`sync_to_async()`](#asgiref.sync.sync_to_async) 的 `thread_sensitive` 模式。

### `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()`](#asgiref.sync.sync_to_async) 有两个线程模式：

- `thread_sensitive=True` （默认使用）：同步函数将与所有其它 `thread_sensitive` 函数在相同线程里运行。如果主线程是同步的并且你正在使用 [`async_to_sync()`](#asgiref.sync.async_to_sync) 装饰器，则该同步函数将成为主线程。
- `thread_sensitive=False`：同步函数将在一个全新的线程中运行，该线程一旦完成，将会关闭。

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

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

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

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

In practice this restriction means that you should not pass features of the
database `connection` object when calling `sync_to_async()`. Doing so will
trigger the thread safety checks:

```pycon
# 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.
```

Rather, you should encapsulate all database access within a helper function
that can be called with `sync_to_async()` without relying on the connection
object in the calling code.
