为什么用 async 异步的 FastAPI 阻塞了所有请求?

文章探讨了在将FastAPI中的同步函数改为异步函数后,由于event_loop的行为变化导致的阻塞问题。作者解释了为何同步操作在异步上下文中会阻塞其他请求,并给出了异步编程的最佳实践,如使用异步库、避免CPU密集操作等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么用 async 异步的 FastAPI 阻塞了所有请求?

将 def 改成 async def 之后

这是一个简单的 hello 路由

# file: app.py

from fastapi import FastAPI

app = FastAPI()
@app.get("/hello")
def hello_world():
    print("Normal request")
    return "Hello World"

从同步编程过来的同学,写了这样的代码:
请求 10s 后响应客户端

# need improve
@app.get("/sync")
def read_item():
    print("Receive sync request")
    time.sleep(10)
    return "sync Hello World!"

在收到 /sync 响应之前,服务器仍然可以正常响应 /hello 请求。

为了跟上异步编程的潮流,这位同学把代码改成:

@app.get("/async")
async def read_item():
    print("Receive async request")
    time.sleep(10)
    return "async Hello World!"

然而这次收到 /sync 响应之前,服务器已经无法接收任何请求!

异步和同步路由的处理差异

源码解析,分析 FastAPI routing.py 的 get_request_handler,有三个阶段需要由 FastAPI 执行用户定义的函数:

  1. 遍历执行 Depends 依赖
  2. 执行用户定义的 endpoint 函数
  3. 据用户定义的 endpoint 类型,决定序列化响应同步还是异步
def get_request_handler(
    dependant: Dependant,
    ... # 忽略其他参数
    dependency_overrides_provider: Optional[Any] = None) -> Callable[[Request], Coroutine[Any, Any, Response]]:
    assert dependant.call is not None, "dependant.call must be a function"
    is_coroutine = asyncio.iscoroutinefunction(dependant.call)
    is_body_form = body_field and isinstance(body_field.field_info, params.Form)
    ... # 忽略部分代码
        # ---- 执行依赖入口 ----
        solved_result = await solve_dependencies(...)
        values, errors, background_tasks, sub_response, _ = solved_result
        if errors:
            raise RequestValidationError(errors, body=body)
        else:
            # ---- 执行路由入口 ----
            raw_response = await run_endpoint_function(
                dependant=dependant, values=values, is_coroutine=is_coroutine
            )

            ... # 忽略部分代码
            # ---- 序列化入口 ---- 
            response_data = await serialize_response(
                ... # 忽略其他参数
                is_coroutine=is_coroutine,
            )
            ... # 忽略部分代码
            return response

    return app

再往下走一层,发现了这样的共同点:

同步的端点跑在 run_in_threadpool ,而异步的方法(看上去)直接顺序执行

# 执行依赖
async def solve_dependencies(
....
):	    ....
        elif is_coroutine_callable(call):
            solved = await call(**sub_values)
        else:
            solved = await run_in_threadpool(call, **sub_values)

# 执行路由函数
async def run_endpoint_function(
    *, dependant: Dependant, values: Dict[str, Any], is_coroutine: bool
) -> Any:
	...
    if is_coroutine:
        return await dependant.call(**values)
    else:
        return await run_in_threadpool(dependant.call, **values)

# 序列化
async def serialize_response(...) -> Any:
    ...
        if is_coroutine:
            value, errors_ = field.validate(response_content, {}, loc=("response",))
        else:
            value, errors_ = await run_in_threadpool(
                field.validate, response_content, {}, loc=("response",)
            )

这会有什么问题?

为何阻塞?

回过头看 Python 异步编程文档

事件循环(event_loop)在线程(通常是主线程)中运行,并执行其线程中的所有回调和任务。

当任务在 event_loop 中运行时,同一线程中不能运行其他任务

不应(SHOULD NOT)直接调用阻塞(Blocking IO)或 CPU 密集(CPU-Bound)代码。

例如,如果一个函数执行 1 秒的 CPU 密集型计算,则所有并发的 asyncio 任务和 IO 操作都会延迟 1 秒。

读取文件、发送网络请求、读取数据库这类 IO 操作,使用异步库,Python 将不等待中间的 IO 操作完成,而是暂停当前的任务,返回一个 coroutine,处理接下来的操作。

当我们将上方调用同步 time.sleep 方法简单地改成 async 方法,event_loop 执行 time.sleep 将等待 10s 而不是暂停执行其他任务。

在最初版本的同步方法,因为调用 run_in_threadpool ,实际是在另一个线程执行同步阻塞操作。

改成 async 变成在同一个线程执行阻塞操作,当只有一个 worker 时,其他请求自然就无法执行了。

异步编程的最佳实践

  1. 异步方法中应当尽量使用异步库,将同步方法简单加 async 修饰可能起到反作用
  2. 异步方法中,无法使用异步库的 IO 操作,应当使用 event_looprun_in_executor 方法
  3. 异步方法中,存在 CPU 密集操作时,调用 run_in_executor 方法应当使用进程池作为 executor
  4. 开发过程中,使用loop.set_debug(enabled=True) 可以方便找出执行时间长的阻塞操作。
# 需在创建 FastAPI 之前
loop = asyncio.get_event_loop()
loop.set_debug(enabled=True)

app = FastAPI()

在本文中,请求 /async 接口就会产生类似下方的日志:

INFO:asyncio:<Server sockets=(<asyncio.TransportSocket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 50000)>,)> is serving
Receive sync request
INFO:     127.0.0.1:51930 - "GET /async HTTP/1.1" 200 OK
WARNING:asyncio:
Executing <Task...run_asgi() done, 
defined at .../h11_impl.py:392> result=None created at .../http/h11_impl.py:240> 
took 10.014 seconds

time.sleep 换成 await asyncio.sleep(10) 日志便消失了,说明没有阻塞 event_loop 的 IO 操作了。

WeChat Official Account: 程序员的碎碎念

<think>好的,我现在需要回答用户关于FastAPI的定义及其优势的问题。用户还希望了解为什么应该使用FastAPI。首先,我应该先回顾一下FastAPI的基本信息,确保自己理解正确。 根据用户提供的引用内容,引用[1]提到了FastAPI的日志、中间件和BaseModel类,说明FastAPI有完善的日志记录和中间件支持,并且使用Pydantic的BaseModel进行数据验证。引用[2]中提到了FastAPI是FARM栈的一部分,用于Web开发,可能具有高效和现代的特性。引用[3]则详细列出了FastAPI的各个功能模块,比如请求参数验证、响应处理、依赖注入系统、安全认证、数据库操作等,这些都可能成为其优势。 接下来,我需要整理FastAPI的定义。FastAPI是一个用于构建API的现代、高性能的Python框架,基于标准的Python类型提示,并且使用异步编程支持。这一点可能需要结合用户提供的引用内容中的信息,例如使用Pydantic进行数据验证,以及异步支持带来的高性能。 关于优势,首先应该是高性能,因为基于Starlette和Pydantic,这两个库都是高性能的。其次,开发效率高,因为自动生成交互式API文档,减少手动编写文档的工作量。另外,类型提示和自动验证也是优势,能减少错误,提高代码可维护性。异步支持在处理高并发请求时表现出色,这也是现代Web框架的重要特性。还有依赖注入系统,可以方便地管理组件,提高代码的模块化和复用性。安全性方面,内置OAuth2、JWT等支持,帮助开发者轻松实现安全认证。 在用户的使用原因方面,可能需要结合这些优势来说明,比如适合需要高性能API的场景,或者需要快速开发的团队,以及需要良好文档支持的项目。同时,对于微服务架构,FastAPI的轻量级和高效性也很适合。 需要确保回答结构清晰,分点列出定义和优势,可能使用加粗标题,但根据用户要求,使用中文,并且行内公式用$...$,不过这个问题可能不涉及数学公式。但需要检查是否有需要格式化的地方,比如代码块,如果有示例代码的话,但用户示例中的回答没有代码,所以可能不需要。不过用户提供的示例问题中,快速排序有代码块,但当前问题可能不涉及代码。 另外,用户要求生成相关问题,需要根据回答内容提出相关的问题,例如关于FastAPI的安装、性能比较、如何实现认证、依赖注入的具体使用等。 最后,引用需要正确标注,用户提供的引用1、2、3需要对应到回答中的合适位置。例如,引用[3]提到了依赖注入系统,可以在依赖注入部分添加引用标识。</think>### FastAPI的定义及核心优势 #### 1. FastAPI的定义 **FastAPI** 是一个基于 Python 的现代 Web 框架,专为构建高性能 API 设计。其核心特性包括: - **基于标准类型提示**:利用 Python 3.6+ 的类型提示语法,结合 Pydantic 库实现请求/响应数据的自动验证与序列化[^1][^3]。 - **异步支持**:原生支持异步请求处理,适合高并发场景[^2]。 - **自动生成文档**:内置 Swagger UI 和 ReDoc,自动生成交互式 API 文档。 #### 2. 使用FastAPI的六大优势 1. **极高性能** FastAPI 基于 Starlette(高性能 ASGI 框架)和 Pydantic(高效数据校验),性能接近 Node.js 和 Go 框架。例如,单个请求处理时间可控制在微秒级别[^2]。 2. **开发效率高** - **代码即文档**:通过类型提示自动生成 API 文档,减少手动维护成本[^1]。 - **智能错误提示**:开发阶段即可捕获数据类型错误,缩短调试时间。 3. **强类型安全保障** 使用 Pydantic 模型定义数据结构,自动验证请求参数: ```python from pydantic import BaseModel class User(BaseModel): name: str age: int = 18 # 带默认值的可选参数 ``` 若客户端传入非法类型(如字符串类型的 `age`),框架会直接返回 422 错误。 4. **异步阻塞架构** 支持 `async/await` 语法,轻松实现高并发处理: ```python @app.get("/items/{item_id}") async def read_item(item_id: int): await database_query(item_id) # 异步数据库操作 return {"item_id": item_id} ``` 适用于 I/O 密集型场景(如微服务、实时数据处理)。 5. **模块化依赖注入** 通过依赖注入系统管理业务逻辑组件: ```python from fastapi import Depends def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.post("/users") def create_user(db: Session = Depends(get_db)): # 使用数据库会话 ``` 这种方式提高了代码复用性和可测试性。 6. **企业级安全特性** 内置 OAuth2、JWT 等安全方案: ```python from fastapi.security import OAuth2PasswordBearer oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @app.get("/protected") async def protected_route(token: str = Depends(oauth2_scheme)): # 验证令牌逻辑 ``` 可快速实现身份认证与权限控制。 ### 典型应用场景 - **微服务架构**:轻量级特性适合作为独立服务单元 - **数据科学接口**:快速部署机器学习模型 API - **实时应用**:WebSocket 支持实时通信 - **移动应用后端**:高效处理 JSON 数据
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值