← 返回题目列表

FastAPI 中 ASGI 和异步 async/await 是什么关系?

高频 中等 第 11 / 27 题 更新于 2026/07/27
ASGIasyncawait事件循环

简化版

ASGI 是 Python 异步 Web 服务的接口规范,FastAPI 运行在 ASGI 之上,因此可以用 async/await 编写非阻塞接口。async/await 只有在内部调用异步 I/O 时才真正发挥作用,如果里面调用同步阻塞代码,仍然会拖慢事件循环。

详细版

ASGI 规定了应用和服务器之间如何用异步方式通信,Uvicorn、Hypercorn 这类服务器负责运行 ASGI 应用。FastAPI 基于 Starlette,而 Starlette 是 ASGI 框架,所以 FastAPI 天然支持异步请求处理。

在 FastAPI 中:

  • async def 路由会在事件循环中执行;
  • def 路由通常会被放到线程池中执行,避免阻塞事件循环;
  • 异步接口内部应该使用异步库,例如 httpx.AsyncClient、异步数据库驱动、异步 Redis 客户端;
  • 如果在 async def 里调用 time.sleep()、同步 requests、同步数据库查询,就会阻塞事件循环。

正确理解是:ASGI 提供异步运行环境,async/await 是编写异步协程的语法,FastAPI 把二者结合起来用于 Web API 开发。

完整版教学

一、先理解 WSGI 和 ASGI 的区别

WSGI 是 Python 传统 Web 生态里的同步接口规范,Flask、Django 早期主要运行在 WSGI 模型下。它的模型简单直接:服务器收到请求,调用应用对象,应用返回响应。这个模型非常成熟,但它天然是同步调用模型。

ASGI 可以看作 WSGI 的异步升级版。它不仅支持 HTTP,还支持 WebSocket、长连接、后台事件等更现代的通信方式。ASGI 应用是一个异步可调用对象,服务器和应用之间通过事件消息通信。

FastAPI 基于 Starlette,Starlette 是 ASGI 框架,所以 FastAPI 能自然处理异步请求、WebSocket、流式响应等场景。

二、async/await 到底做了什么

async def 定义的是协程函数,调用它不会立刻执行完整逻辑,而是返回一个协程对象。await 表示当前协程遇到了一个可以等待的异步操作,比如异步网络请求、异步数据库查询。等待期间,事件循环可以去调度其他协程。

举个直观例子:餐厅只有一个服务员。如果服务员给一桌客人下单后站在厨房门口一直等菜,这就是同步阻塞。如果服务员下单后去服务其他桌,等厨房通知菜好了再回来,这就是异步 I/O 的感觉。

在 FastAPI 里,一个请求等待数据库返回时,如果使用异步驱动,事件循环可以继续处理其他请求。这样同样的线程资源可以支撑更多并发连接。

三、FastAPI 如何处理 async def 和 def

FastAPI 支持两种路由函数:

@app.get("/async")
async def async_api():
    return {"mode": "async"}

@app.get("/sync")
def sync_api():
    return {"mode": "sync"}

async def 会作为协程在事件循环中运行。它适合内部使用异步 I/O 的接口,例如:

import httpx

@app.get("/remote")
async def call_remote():
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://example.com")
    return {"status": resp.status_code}

普通 def 函数通常会被 FastAPI 放入线程池执行。这样即便里面有同步阻塞调用,也不至于直接卡住事件循环。对于仍然使用同步 ORM 或同步 SDK 的项目,def 路由反而可能更安全。

四、为什么 async def 里不能随便调用同步阻塞代码

事件循环的特点是“协作式调度”。协程必须在遇到 await 时主动交出控制权。如果你在 async def 中写:

import time

@app.get("/bad")
async def bad_api():
    time.sleep(3)
    return {"ok": True}

这 3 秒里事件循环被同步阻塞,其他协程没有机会运行。并发请求一多,整个服务就会出现延迟升高。

类似的问题还包括:在异步函数里使用 requests.get()、同步数据库客户端、大量 CPU 循环、同步文件大读写等。它们都会让 async 失去意义。

五、什么时候用 async,什么时候不用

如果接口主要调用异步数据库、异步 HTTP、异步缓存、异步消息队列,那么适合用 async def。如果你的项目依赖同步 SQLAlchemy session、同步 requests、同步第三方 SDK,可以先用普通 def,让 FastAPI 把它放到线程池。

CPU 密集型任务不要寄希望于 async。比如图片压缩、视频处理、大模型推理、复杂加密计算,这些任务会占用 CPU,应该放到进程池、Celery/RQ 任务队列,或者拆到独立计算服务。

六、面试表达的关键点

面试官问 ASGI 和 async 的关系,真正考的是你是否理解异步并发的边界。好的回答应包含三点:FastAPI 运行在 ASGI 上;async/await 让 I/O 等待期间释放事件循环;同步阻塞代码会破坏异步收益。

还可以补一句:异步不是为了让单个请求一定更快,而是为了在 I/O 等待多的场景下提高整体吞吐和资源利用率。

七、常见误区与追问

写法适合场景风险
async def + 异步 I/Ohttpx.AsyncClient、AsyncSession、异步 Redis依赖库不支持异步时收益消失
普通 def + 同步 I/O同步 ORM、同步 SDK、遗留代码依赖线程池容量,阻塞多会排队
async def + 同步阻塞不推荐会卡住事件循环,影响同进程其他协程
1 个事件循环处理多个协程:
t=0ms   请求A await 数据库
t=1ms   事件循环切到请求B
t=2ms   请求B await HTTP
t=200ms 请求A I/O 返回后继续执行

易错点:async 是协作式并发,只有遇到真正可等待的 I/O 才会让出执行权。

  • 误区:把路由改成 async def 就一定提升性能。 如果里面调用 requests、同步数据库驱动、time.sleep() 或 CPU 大循环,事件循环仍然会被阻塞。
  • 误区:ASGI 等于 FastAPI 独有能力。 ASGI 是 Python 异步 Web 规范,Uvicorn、Hypercorn 是常见 ASGI 服务器,FastAPI 是运行在 ASGI 生态上的框架。
  • 误区:异步会让单个数据库查询更快。 异步通常提升的是等待期间的并发处理能力,不会缩短数据库实际执行 SQL 的时间。
  • 追问:FastAPI 中普通 def 路由怎么处理? FastAPI 会把同步路径操作放到线程池中执行,避免直接阻塞事件循环,但线程池也有容量和调度成本。
  • 追问:WebSocket 为什么更适合 ASGI? WebSocket 是长连接和事件驱动通信,ASGI 的双向消息模型比传统同步 WSGI 更自然。
  • 追问:怎么判断线上是否被同步阻塞拖慢? 观察事件循环延迟、请求耗时分布、worker CPU、线程池排队和慢调用日志;如果少量慢同步调用导致大量请求一起变慢,通常就是阻塞信号。

八、加强记忆

记住这条线:ASGI 是异步 Web 规范,Uvicorn 是常见 ASGI 服务器,FastAPI 是 ASGI 应用,async/await 是写异步逻辑的语法。异步收益来自“等待 I/O 时不占着事件循环”,不是来自把所有函数都改成 async def