FastAPI中async def与def对比:为何阻塞I/O操作不推荐使用async def
FastAPI阻塞I/O操作不推荐使用async def的核心原因
1. 事件循环的运行机制限制
FastAPI的异步路由运行在单线程的事件循环之上,这个模式的核心规则是:
- 同一时间事件循环只能处理一个任务,只有当前任务主动通过
await让出执行权时,事件循环才会调度执行其他待处理的任务 - 阻塞I/O操作(比如同步HTTP请求
requests.get()、同步文件读写、time.sleep()、同步数据库查询等)不会主动让出执行权,会直接占用整个事件循环直到操作完成 - 一旦async def定义的路由中出现阻塞I/O,在阻塞的整个时间段内,服务所有的异步接口、后台异步任务都会被卡住,没有任何新请求能被处理,等同于服务临时假死
2. FastAPI对不同定义方式的路由的调度差异
FastAPI对def和async def定义的路由有完全不同的调度逻辑:
- 如果你用普通
def定义路由,FastAPI会自动将函数提交到独立的线程池中运行,阻塞操作只会阻塞当前所属的单个线程,不会影响事件循环的正常调度,其他请求依然可以被正常处理 - 如果你用
async def定义路由,FastAPI会直接将函数放到事件循环中执行,不会额外分配线程资源,此时函数内部的所有阻塞操作都会直接卡断整个事件循环的调度
3. 适用场景边界澄清
并不是说async def不能使用,而是只有满足以下所有条件时才适合用async def定义路由:
- 路由内部的所有I/O操作均为异步非阻塞实现
- 所有异步操作都添加了
await关键字主动让出执行权
比如使用httpx.AsyncClient做异步网络请求、aiopg做异步PostgreSQL查询、用asyncio.sleep替代time.sleep,这类场景下使用async def才能获得高并发收益
如果你的代码中包含无法改造为异步实现的同步阻塞逻辑,直接用普通def定义路由即可,FastAPI内置的线程池调度已经做了充分优化,性能远高于硬写async def加阻塞操作的实现,且稳定性更高。
示例对比
错误用法:async def中加入阻塞I/O
import time from fastapi import FastAPI app = FastAPI() @app.get("/block-test") async def block_test(): # 同步sleep会卡死事件循环10秒,期间所有请求都无法响应 time.sleep(10) return {"status": "done"}
正确用法1:用普通def封装同步阻塞逻辑
import time from fastapi import FastAPI app = FastAPI() @app.get("/sync-test") def sync_test(): time.sleep(10) return {"status": "done"}
正确用法2:async def搭配异步非阻塞操作
import asyncio from fastapi import FastAPI app = FastAPI() @app.get("/async-test") async def async_test(): # 异步sleep会主动让出执行权,不影响其他请求处理 await asyncio.sleep(10) return {"status": "done"}
内容的提问来源于stack exchange,提问作者Diogo Crava
相关产品推荐
相关产品推荐

