首页 / FastAPI 入门教程 / 异步支持 async/await

FastAPI 入门教程

异步支持 async/await

本教程共 50 篇 · 第 38 篇 · 更新于 2026-08-12 · 约 8 分钟阅读

FastAPIFastAPI 入门教程异步async/awaitASGI协程

本节目标:搞懂 FastAPI 的异步是怎么回事,学会用 async def 和 await 写接口,并明白为什么它能扛住大量并发请求。

你点开一个网页,服务器收到请求,去查数据库、读文件、调别的服务,这些都要等。等的时候如果让程序干站着,就是浪费。FastAPI 的厉害之处,就在于它擅长在”等待”的时候去干别的活。这一章我们就讲清楚它背后的异步机制。

38-1 什么是异步,什么是 ASGI

FastAPI 跑在 ASGI 服务器上(默认是 Uvicorn),ASGI 是 Python 异步 Web 服务的标准接口。和老式的 WSGI 不同,ASGI 天生支持异步,能让一个进程同时照看很多个请求。

打个比方。你去快餐店点餐,付完钱拿到一个号码牌。这时你不用站在柜台前傻等,而是可以端着饮料坐下来,和朋友聊天。等厨房做好了,屏幕上显示你的号码,你再去取餐。

服务器处理请求也是一样。用户发来请求后,程序可能要等数据库返回结果。在等的过程中,FastAPI 不会让这个线程空转,它会转去处理别的用户发来的请求。等数据库结果回来了,再回头把数据拼好返回。这种”等的时候去干别的”就是异步。

Note

异步不等于”同时读两个文件”。它指的是:在等待某个慢操作(网络、磁盘、数据库)时,腾出手处理别的请求。真正同时算多件事叫”并行”,那是多进程/多核的事。

38-2 用 async def 写路径操作

在 FastAPI 里,写接口函数时前面加 async def,它就成了一个异步函数。这是 FastAPI 的默认推荐写法。

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
    return {"message": "Hello World"}

这里的 async def read_root() 就是”异步路径操作函数”。FastAPI 看到 async def,就知道这个函数可以在运行到 await 时主动让出控制权,让事件循环去处理别的请求。

启动它还是用熟悉的命令:

fastapi dev main.py

等价的 Uvicorn 命令是:

uvicorn main:app --reload

38-3 await 与协程

async def 函数里可以用 awaitawait 的意思是:这里要等一个”慢活”完成,先让出控制权。

await 的东西通常是一个”协程”(coroutine),也就是另一个 async def 函数返回的对象。简单说:协程就是可以被暂停、可以中途让位的函数。

import httpx
from fastapi import FastAPI

app = FastAPI()


async def fetch_data():
    # httpx.AsyncClient 支持 await,是个异步客户端
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://example.com/api")
    return resp.json()


@app.get("/proxy")
async def proxy():
    data = await fetch_data()
    return {"data": data}

注意两点。第一,await 只能写在 async def 函数里面。第二,凡是用了 await 调用的函数,它本身也得是 async def。就像接力的第一棒由 FastAPI 帮你跑——你的路径操作函数就是那个入口,FastAPI 会正确地驱动它。

Tip

await 调用的第三方库,必须是”支持异步”的库。比如发 HTTP 请求用 httpx.AsyncClient,而不是同步的 requests;查数据库用异步驱动(如 asyncpgdatabases)。如果你用的库不支持 await,请看下一章怎么处理。

38-4 协程到底是个啥

“协程”是个听起来高级的词,其实它就是 async def 函数返回的那团东西。Python 知道它是一个能开始、能结束、中途还可能在 await 处暂停的对象。

你可以把它类比成 Go 语言的 goroutine,或者 Node.js 里的异步任务。它们本质都是”轻量的、能互相让位的执行单元”,比线程更省资源。FastAPI 底层用的 Starlette,正是靠这些协程来实现高性能的。

下面这段用 asyncio.sleep 演示”让位”。await asyncio.sleep(1) 模拟一次耗时一秒的等待,期间事件循环可以去处理别的请求:

import asyncio
from fastapi import FastAPI

app = FastAPI()


@app.get("/slow")
async def slow_task():
    # 模拟一次等待(比如查数据库)
    await asyncio.sleep(1)
    return {"done": True}

如果一个用户访问 /slow 要等一秒,这一秒里 FastAPI 照样能响应其他用户的请求。这就是异步带来的并发能力。

38-5 并发处理多个请求

Web 应用最常见的情况就是:很多用户同时连上来,但每个人都要等自己的慢操作(网络、数据库)。等待时间累加起来很可观,单个请求可能只等几毫秒,但成千上万个请求加起来就是巨大的空闲。

异步正好解决这个问题。FastAPI 用一个事件循环,在”谁在等”和”谁该继续”之间来回切换。你写 async def 时,FastAPI 自动帮你调度,不需要自己管线程。

我们可以同时定义多个接口,它们互不阻塞:

import asyncio
from fastapi import FastAPI

app = FastAPI()


@app.get("/a")
async def handle_a():
    await asyncio.sleep(0.5)
    return {"endpoint": "a"}


@app.get("/b")
async def handle_b():
    await asyncio.sleep(0.5)
    return {"endpoint": "b"}

当请求 A 在 await 时,请求 B 立刻能被处理。两个请求几乎同时完成,而不是排队各等半秒。对浏览器前端来说,体验就是”快”。

Note

想要最大性能,强烈建议路径操作里调用的库都是异步的。这样整条链路都不阻塞,FastAPI 才能把并发优势发挥到极致。

38-6 什么时候该用 async,什么时候用 def

这是新手最容易糊涂的地方。给一个简单判断法:

如果你用的第三方库明确写着”请用 await 调用”(比如异步数据库驱动、异步 HTTP 客户端),那你的路径操作函数就用 async def,并在里面 await 它。

@app.get("/items")
async def read_items():
    results = await some_async_library()
    return results

如果你用的库不支持 await(目前大多数传统数据库库都不支持),那就用普通的 def 写路径操作:

@app.get("/items")
def read_items():
    results = some_sync_library()
    return results

不用慌,即使用 def,FastAPI 照样异步、照样快。它会把这个 def 函数丢到一个外部线程池里执行,不阻塞主事件循环。你甚至可以混着用:有的接口 async def,有的接口 def,FastAPI 都能正确处理。

如果你根本不确定该用哪个,先用普通 def 就对了。FastAPI 会在背后做合适的优化。

还有一句贴心话:对于毫秒级的轻量计算(比如拼个字符串、做个简单数学运算),在 FastAPI 里写 async def 反而更省——写成 def 会被丢进线程池,多出一点点线程调度开销。但如果是耗时较长的 CPU 密集计算(大批数据处理、复杂运算),正确做法是写 def 让它进线程池,别卡住事件循环(下一章详讲)。判断标准很简单:里面没有阻塞 I/O、算得又快,就用 async def

38-7 小结与自检

这一章我们认识了异步的精髓:在等待慢操作时腾出手处理别的请求。你学会了:

  • FastAPI 基于 ASGI,天生支持异步,默认推荐 async def
  • await 只能在 async def 里用,用来等待一个协程(另一个异步函数)。
  • 多个请求可以在一个事件循环里并发推进,互不干等。
  • 调异步库用 async def;调不支持 await 的库用 def,FastAPI 会丢进线程池。

下一章我们专讲”阻塞代码”该怎么处理,以及 run_in_threadpool 这个工具怎么用。