Skip to content

11. FastAPI 异步处理 ​

异步解决的是等待时的空闲 ​

接口调用数据库、等待远程 HTTP、读取网络流时,经常在“等”。异步让事件循环在等待期间处理其他任务。它提高 I/O 并发能力,不会自动加速 Python 的密集计算。

async def 定义协程函数;调用后产生协程对象;await 等待结果,并在可等待操作尚未完成时让出执行机会。只加 async 却在里面调用阻塞函数,仍然会阻塞事件循环。

两种路由怎么选 ​

python
import asyncio
import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/async-wait")
async def async_wait():
    await asyncio.sleep(1)
    return {"mode": "异步等待"}

@app.get("/sync-wait")
def sync_wait():
    time.sleep(1)
    return {"mode": "在线程池中等待"}

保存为 main.py 并启动。普通 def 路由由框架放进线程池;上面的同步等待不会直接卡住事件循环,但会占用线程池容量。不要把 time.sleep(1) 放到 async def 中。

普通工具函数不会因为从异步路由调用就自动进线程池。遇到已有阻塞库,可明确使用线程池桥接:

python
from starlette.concurrency import run_in_threadpool

def blocking_work():
    time.sleep(1)
    return "完成"

@app.get("/bridge")
async def bridge():
    result = await run_in_threadpool(blocking_work)
    return {"result": result}

并发不是简单开更多任务 ​

追加以下路由,三个异步等待可并发执行,而不是顺序等待三次:

python
@app.get("/parallel")
async def parallel():
    async with asyncio.timeout(3):
        result = await asyncio.gather(
            asyncio.sleep(1, result="A"),
            asyncio.sleep(1, result="B"),
            asyncio.sleep(1, result="C"),
        )
    return {"results": result}

真实远程调用应设置连接超时、读取超时、连接池上限,并通过信号量限制并发。无上限 gather 一百万个任务,通常先耗尽内存或压垮下游。

gather 中一个任务失败时,不应想当然认为其他任务全部被取消;需要更明确的结构化取消语义时可考虑 Python 3.11+ 的 TaskGroup。取消后用 finally 关闭资源,不要随意吞掉取消异常。

与数据库结合 ​

PyMySQL 是同步驱动:本系列数据库路由用 def。要改为真正异步,需要异步数据库驱动、create_async_engine 和 AsyncSession 一起调整,不能只把函数头改为 async def。

CPU 密集型任务考虑独立进程或任务服务。共享字典、计数器和文件写入也不因异步而天然安全;每个请求拥有独立 Session,同一个 AsyncSession 也不要在并发任务间共享。

练习:分别顺序等待三次、使用 gather 等待三次,测量耗时;再用两个浏览器请求观察并发效果。耗时受机器和调度影响,不把示例时间当性能承诺。