首页
/ FastAPI 生命周期事件深入解析:在启动前初始化、关闭时释放资源的完整方案

FastAPI 生命周期事件深入解析:在启动前初始化、关闭时释放资源的完整方案

2026-09-07 10:21:49作者:庞队千Virginia

生命周期事件(Lifespan Events)是 FastAPI 应用管理"全局共享资源"的官方入口:它允许你在应用开始接收请求之前恰好执行一次启动逻辑,并在应用处理完所有请求关闭之前执行一次收尾逻辑。本指南以 docs/es/docs/advanced/events.md(对应官方英文文档 events.md)为主线,结合本仓库 docs_src/events 中的完整示例与 tests 中的测试代码,系统讲解现代推荐的 lifespan 写法、Python 异步上下文管理器原理、已废弃的 startup/shutdown 事件方案,以及它们在子应用与测试环境下的真实行为。读完你将能正确管理数据库连接池、共享机器学习模型、GPU 或内存等需要在应用进程生命周期内共享与清理的资源。

为什么需要"应用启动前 / 关闭后"的执行时机

正常情况下,FastAPI 的接口代码只覆盖请求到来之后的这段时间。但你经常需要处理这样两类逻辑:

  • 应用启动时:创建全局共享资源,例如建立数据库连接池、加载一个所有请求共用的机器学习模型。
  • 应用关闭时:释放这些资源,例如关闭连接、释放 GPU/内存、写入收尾日志。

这类代码应当在应用开始接收请求之前执行一次,并在可能处理了成千上万个请求之后、进程退出前执行一次。由于它正好覆盖应用从启动到关闭的完整时间段,官方文档用 "lifespan"(生命周期)这个词来称呼它。

典型使用场景:共享机器学习模型

想象一个最典型的需求场景:你有若干机器学习模型需要用来处理请求,所有请求共享同一份模型(不是每请求一个、每用户一个)。加载模型可能需要从磁盘读取大量数据、耗时较长,显然不能为每个请求重复加载。

一个直觉做法是在模块/文件顶层直接加载:

# 不推荐:模块顶层加载
model = load_model()  # 无论是否需要处理请求,导入模块就会执行

但这样做有个严重副作用:只要你导入这个模块,模型就会被加载。如果此时你只想跑一个与该模型无关的独立自动化测试,导入过程就会被迫等待模型加载完成,测试因此被拖慢。文档原话指出:加载模型应当在请求被处理之前发生,但只应在应用即将开始接收请求的那个时刻,而不是代码被加载的时刻。这正是 lifespan 机制要解决的问题。

现代推荐写法:通过 lifespan 参数 + async context manager

文档强调:定义 startup 与 shutdown 逻辑的推荐方式是使用 FastAPI() 构造时的 lifespan 参数,并搭配一个"上下文管理器"。

先看本仓库 docs_src/events/tutorial003_py310.py 提供的完整示例:

from contextlib import asynccontextmanager

from fastapi import FastAPI


def fake_answer_to_everything_ml_model(x: float):
    return x * 42


ml_models = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 加载 ML 模型(模拟昂贵的启动操作)
    ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
    yield
    # 清理 ML 模型并释放资源
    ml_models.clear()


app = FastAPI(lifespan=lifespan)


@app.get("/predict")
async def predict(x: float):
    result = ml_models"answer_to_everything"
    return {"result": result}

逐段理解这个示例:

  • yield 之前的部分:在启动阶段执行。示例把"模型"塞进 ml_models 字典,模拟一次昂贵的模型加载。它会在应用开始接收请求之前运行。
  • yield 之后的部分:在应用处理完所有请求、即将 shutdown 时执行。示例调用 ml_models.clear() 把模型移出内存,实际工程里这里通常做释放内存、GPU 显存或关闭连接的收尾工作。
  • shutdown 发生的时机是停止应用的时候,例如发布新版本需要重启服务,或你主动结束运行。文档还给了个幽默注释:"或者你只是厌倦了运行它 🤷"。

yield 异步生成器与 yield 依赖的相似性

注意一个关键点:这里定义的 lifespan() 是一个带 yieldasync def 函数,与 FastAPI 的"带 yield 的依赖项"(Dependencies with yield)在形态上非常接近。本质上 FastAPI 把这类函数当作一个生成器来驱动:

  • yield 之前的分支在应用开始前执行;
  • yield 之后的分支在应用结束后执行。

装饰器 @asynccontextmanager:把函数变成 async context manager

示例中的函数被 @asynccontextmanager 装饰,从而转换为一种被称为 async context manager(异步上下文管理器) 的对象。

普通 Python 开发者都熟悉同步 context manager,例如 open() 可以用在 with 语句中:

with open("file.txt") as file:
    file.read()

较新版本的 Python 还支持异步上下文管理器,配合 async with 使用:

async with lifespan(app):
    await do_stuff()

无论是 context manager 还是 async context manager,其执行契约是相同的:进入 with/async with 代码块之前,先执行 yield 之前的代码;退出代码块时,执行 yield 之后的代码

在我们的示例里并没有直接手写 async with lifespan(app)——我们把 lifespan 这个 async context manager 整体交给 FastAPI,由框架在合适时机自动进入/退出它。FastAPI 应用的 lifespan 参数正是接收一个 async context manager,因此可以像第 22 行那样直接传 lifespan=lifespan

从源码角度看,fastapi/applications.pyFastAPI.__init__lifespan 参数(约 L527-L538)的类型标注为 Lifespan[AppType] | None,其文档字符串明确说明:"一个 Lifespan 上下文管理器处理器,用一个 context manager 取代 startupshutdown 函数"。该值随后与 on_startupon_shutdown 一起透传给底层的路由/ASGI 应用构造。

用测试代码验证 startup / shutdown 的执行时序

仓库 tests/test_tutorial/test_events/test_tutorial003.py 用 TestClient 严格验证了上述时序:

from fastapi.testclient import TestClient
from inline_snapshot import snapshot

from docs_src.events.tutorial003_py310 import (
    app,
    fake_answer_to_everything_ml_model,
    ml_models,
)


def test_events():
    assert not ml_models, "ml_models should be empty"
    with TestClient(app) as client:
        assert ml_models["answer_to_everything"] == fake_answer_to_everything_ml_model
        response = client.get("/predict", params={"x": 2})
        assert response.status_code == 200, response.text
        assert response.json() == {"result": 84.0}
    assert not ml_models, "ml_models should be empty"

这段测试精确印证了文档描述的整个生命周期:

  1. 进入 with TestClient(app) as client 之前ml_models 为空——说明此时启动逻辑尚未执行(这也是使用 lifespan 而非模块顶层加载的好处:不进入应用上下文就不会加载模型);
  2. 进入 with 块、TestClient 启动应用后,ml_models 中已存在模型——yield 前的 startup 逻辑已运行;
  3. client.get("/predict", params={"x": 2}) 正常返回 {"result": 84.0}2 * 42),请求期间共享资源可用;
  4. 退出 with 块后,ml_models 再次为空——yield 后的 shutdown 清理逻辑已被执行。

顺带一提:该文件中的 test_openapi_schema 证明 lifespan 的存在不会影响 OpenAPI schema 的生成/predict 接口照常出现在 /openapi.json 中。

已废弃的替代方案:startupshutdown 事件

文档专门用一整节说明:存在一种更古老的写法——通过 @app.on_event("startup")@app.on_event("shutdown") 注册事件处理器。但这套方案已经废弃(deprecated),源码中 FastAPI.on_eventfastapi/applications.py 约 L4653 起)与 APIRouter.on_eventfastapi/routing.py 约 L6423 起)的文档字符串均带有 @deprecated 装饰器与明确的弃用说明:"on_event is deprecated, use lifespan event handlers instead(on_event 已弃用,请改用 lifespan 事件处理器)"。测试目录中凡使用 on_event 的地方也都需要显式屏蔽 DeprecationWarning,例如 tests/test_router_events.py

文档特别给出警告:推荐方式只有上面介绍的 lifespan 参数。如果提供了 lifespan,则 startup/shutdown 事件处理器将不再被调用。二选一,不能同时使用。(原文:"It's all lifespan or all events, not both.")如果你刚开始接触 FastAPI,文档建议直接跳过本节。

警告
- lifespan 与 startup/shutdown 事件互斥:提供了 lifespan 就不要再注册事件处理器
- on_event 在当前源码中已被 @deprecated 标记
- 事件处理器可以用 async def 或普通 def 声明

尽管如此,了解这套旧写法仍然有价值——大量存量项目还在使用它,遇到老代码时你需要能读懂。

startup 事件示例

注册一个在应用启动前执行的函数,事件类型传 "startup"

from fastapi import FastAPI

app = FastAPI()

items = {}


@app.on_event("startup")
async def startup_event():
    items["foo"] = {"name": "Fighters"}
    items["bar"] = {"name": "Tenders"}


@app.get("/items/{item_id}")
async def read_items(item_id: str):
    return items[item_id]

这段代码来自 docs_src/events/tutorial001_py310.py。这里 startup 处理器向作为内存"数据库"的 items 字典预填了两条数据。文档补充了两个重要行为约定:

  • 可以注册多个 startup 事件处理器;
  • 应用不会开始接收请求,直到所有 startup 处理器都执行完毕

shutdown 事件示例与同步/异步的选择

注册关闭时执行的函数,事件类型传 "shutdown"。示例来自 docs_src/events/tutorial002_py310.py

from fastapi import FastAPI

app = FastAPI()


@app.on_event("shutdown")
def shutdown_event():
    with open("log.txt", mode="a") as log:
        log.write("Application shutdown")


@app.get("/items/")
async def read_items():
    return [{"name": "Foo"}]

这里 shutdown 处理器向 log.txt 追加一行 "Application shutdown"。文档借此引出一个非常实用的选型要点:

  • open()mode="a" 表示 append(追加),新行会接在文件已有内容之后,不会覆盖旧内容;
  • 这里使用的是标准 Python 同步 open(),它涉及磁盘 I/O、"等待"数据落盘,但 open() 本身不基于 async/await
  • 因此处理器声明为普通 def shutdown_event(),而非 async def——当处理器内部调用的是同步阻塞型 I/O 函数时,用普通 def 声明即可,FastAPI 会在后台线程池中运行它,避免阻塞事件循环。

为什么旧方案最终让位于 lifespan

文档点明了旧方案的结构性缺陷:真实项目里 startup 与 shutdown 的逻辑往往是配对的——启动某物就要结束它、获取资源就要释放它。把这些配对逻辑拆成两个互相隔离的函数,只能通过模块级全局变量或类似技巧在两者间传递状态,非常别扭。

lifespan 用一个函数把"启动→运行→关闭"完整地收拢在同一个作用域内:yield 之前初始化、yield 之后清理,中间需要共享的变量天然就是函数局部变量或闭包状态,无需全局变量。这正是它被推荐为现代唯一写法的根本原因。

生命周期事件与子应用 / 挂载路由的边界

文档在最后专门用 🚨 提醒一个极易踩坑的边界:lifespan 事件(startup 与 shutdown)只会针对主应用执行,不会为 Sub Aplicaciones(子应用)(通过 mount 挂载的 ASGI 应用)执行。

也就是说,如果你的 FastAPI 应用用 app.mount("/other", other_app) 挂载了另一个独立的 ASGI 应用,那个子应用自身注册的 lifespan/startup/shutdown 逻辑在父应用的生命周期里不会被触发——挂载的子应用本质上是被当作一个普通路由/中间件处理对象接入的,其进程级生命周期由父应用统一管理。如果你确实需要子应用初始化资源,应在父应用的 lifespan 中统一处理,或让子应用自行惰性初始化资源。

需要区分的一个概念是:mount 挂载"独立 ASGI 子应用"与 include_router 包含"同一应用内的路由"并不相同。从本仓库测试 tests/test_router_events.py 可以看到,通过 include_router 纳入的路由器上注册的 on_event("startup")/on_event("shutdown") 处理器(test_router_events),甚至路由器级 APIRouter(lifespan=...) 的启动/关闭逻辑(test_router_nested_lifespan_state 等),都会在应用生命周期中被合并执行——测试用 app_startup / router_startup / sub_router_startup 等状态标志逐阶段断言了这些回调的执行顺序,并验证了多级 lifespan 的 yield 值会按"父级优先、子级覆盖同键"的规则合并。也就是说,本文讨论的"只作用于主应用"限制专指 mount 挂载的独立子应用,而 include_router 引入的路由(含其事件)归属于主应用,会正常生效。

技术细节:这一切底层的 ASGI Lifespan 协议

最后是文档留给好奇者的技术彩蛋:在 ASGI 技术规范层面,这套机制对应 Lifespan Protocol(生命周期协议),它定义了 startupshutdown 两种事件。当 ASGI 服务器(如 Uvicorn)启动应用进程时,会向应用发送 lifespan 消息,驱动我们上面编写的 context manager 完成初始化;服务器优雅停机时,再发送对应的关闭消息,触发 yield 之后的清理逻辑。FastAPI 文档建议进一步阅读 Starlette 官方的 lifespan 处理文档,其中包括如何借助 lifespan 向应用各处注入共享状态(FastAPI 的 Request.state 等机制即与之相关)。从本仓库的 APIRouter 多级 lifespan 测试可以看出,当前实现已支持把 yield 吐出的状态字典逐层合并后暴露给请求处理代码,这是做全链路依赖注入时非常有用的进阶能力。

小结:一张图掌握选择依据

关注点 结论
推荐做法 FastAPI(lifespan=async_context_manager)yield 前启动、yield 后清理
适用资源 数据库连接池、共享 ML 模型、GPU/内存、长连接客户端等全应用共享资源
旧写法 @app.on_event("startup"/"shutdown"),已废弃,源码中带 @deprecated 标记
互斥规则 提供了 lifespan 后,startup/shutdown 事件不再执行,二者只能选其一
覆盖范围 只覆盖主应用;mount 挂载的独立子应用不会被触发
测试方式 with TestClient(app) 进入/退出即完整驱动 startup 与 shutdown(见 tests/test_tutorial/test_events/test_tutorial003.py
底层协议 ASGI Lifespan Protocol 的 startup / shutdown 消息

参考实现与证据文件:完整代码示例见 docs_src/eventslifespan 参数与 on_event 弃用标记见 fastapi/applications.pyfastapi/routing.py,生命周期时序验证见 tests/test_tutorial/test_events/test_tutorial003.pytests/test_router_events.py

登录后查看全文
热门项目推荐
相关项目推荐