
本文详细介绍了如何使用 fastapi 的 lifespan 事件结合 asynccontextmanager 在应用启动后、处理任何请求之前执行一次性初始化任务。通过此机制,开发者可以确保数据库连接、缓存预加载等操作在服务可用时已完成,同时避免阻塞服务器启动过程。
理解应用启动时的初始化需求
在构建基于 FastAPI 的 Web 服务时,我们经常需要在应用程序启动后、开始处理用户请求之前执行一些初始化操作。这些操作可能包括:
- 加载配置数据
- 建立数据库连接池
- 预加载缓存数据
- 启动后台任务或消费者
- 执行其他一次性设置逻辑
直接在 if __name__ == "__main__": 块中调用这些函数,如果放在 uvicorn.run(app) 之前,会阻塞服务器启动,导致服务无法及时响应;如果放在 uvicorn.run(app) 之后,则根本不会被执行,因为 uvicorn.run(app) 是一个阻塞调用,会一直运行直到服务器关闭。因此,我们需要一个机制,让 FastAPI 框架自身来管理这些启动事件。
FastAPI 的 Lifespan 事件管理
FastAPI 提供了 lifespan 事件管理机制,它基于 Python 的 contextlib.asynccontextmanager,允许开发者定义在应用启动和关闭时执行的异步代码。这是处理一次性初始化任务的官方推荐方式。
lifespan 的核心思想是定义一个异步上下文管理器。当应用启动时,上下文管理器会执行 yield 语句之前的代码;当应用准备好接收请求时,它会 yield 控制权,此时服务开始处理请求;当应用关闭时,它会执行 yield 语句之后(即上下文退出时)的代码,用于资源清理。
示例:在 FastAPI 启动后初始化数据
假设我们有一个需求,在 FastAPI 应用启动后,需要执行一个耗时操作(例如,模拟数据加载),然后初始化一个全局变量 DATA。
import time
import uvicorn
from fastapi import FastAPI
from contextlib import asynccontextmanager
# 全局变量,用于存储初始化后的数据
DATA = {"value": ""}
def create_data():
"""
模拟一个耗时的初始化函数,用于在应用启动时加载数据。
"""
print("正在执行 create_data()...")
time.sleep(2) # 模拟耗时操作,例如从数据库加载数据或进行复杂的计算
DATA["value"] = "Hello World!"
print("create_data() 执行完毕。")
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
FastAPI 应用的生命周期管理器。
在应用启动时执行 create_data(),并在应用关闭时执行清理操作(如果需要)。
"""
# --- 应用启动时执行的代码 ---
create_data() # 在这里调用我们的初始化函数
print("FastAPI 应用已启动,准备接收请求。")
yield # 应用在此处开始接收请求,yield 之前的代码在启动前完成
# --- 应用关闭时执行的代码 ---
print("FastAPI 应用正在关闭。")
# 可以在这里执行清理操作,例如关闭数据库连接、释放资源等
DATA["value"] = "" # 清理数据,模拟资源释放
print("清理工作完成。")
# 将 lifespan 事件管理器传递给 FastAPI 应用
app = FastAPI(lifespan=lifespan)
@app.get("/")
def get_root():
"""
一个简单的 GET 接口,返回 DATA 中存储的值。
"""
return DATA
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)代码解析
- from contextlib import asynccontextmanager: 导入 asynccontextmanager 装饰器,它是创建异步上下文管理器的关键。
- DATA = {"value": ""}: 定义一个全局字典,用于存储初始化后的数据。
- def create_data():: 这是一个普通函数,包含了我们希望在应用启动时执行的逻辑。这里模拟了数据加载的耗时操作。
- @asynccontextmanager: 这个装饰器将 lifespan 异步生成器函数转换为一个异步上下文管理器。
-
async def lifespan(app: FastAPI)::
- 这个异步函数是 lifespan 事件的核心。它接收 FastAPI 应用实例作为参数。
- create_data(): 在 yield 语句之前,create_data() 函数会被调用。这意味着在 FastAPI 应用开始处理任何 HTTP 请求之前,create_data() 会先执行并完成。
- yield: 这是上下文管理器的关键点。当执行到 yield 语句时,lifespan 函数会暂停,并将控制权交还给 FastAPI。此时,FastAPI 应用正式启动,可以开始接收并处理客户端请求。
- yield 之后的代码会在应用关闭时执行。这提供了一个方便的钩子来执行清理工作,例如关闭数据库连接、释放内存等。
- app = FastAPI(lifespan=lifespan): 在创建 FastAPI 应用实例时,通过 lifespan 参数将我们定义的 lifespan 上下文管理器传递进去。FastAPI 会在内部管理这个生命周期事件。
- uvicorn.run(app): 正常启动 Uvicorn 服务器。此时,lifespan 中 yield 之前的代码(即 create_data())会先执行。
运行效果
当你运行上述代码时,你会观察到以下输出顺序:
正在执行 create_data()... create_data() 执行完毕。 FastAPI 应用已启动,准备接收请求。 INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: Started reloader process [xxxxx] using statreload INFO: Started server process [xxxxx] INFO: Waiting for application startup. INFO: Application startup complete.
然后,如果你访问 http://localhost:8000,你会得到 {"value": "Hello World!"},这证明 create_data() 已经成功执行并更新了 DATA 变量。
当你按下 CTRL+C 停止服务器时,你会看到:
INFO: Shutting down FastAPI 应用正在关闭。 清理工作完成。 INFO: Finished server process [xxxxx] INFO: Stopped reloader process [xxxxx]
这表明 yield 之后的清理代码也得到了正确执行。
注意事项
- 异步操作: 如果你的初始化函数 create_data 内部包含异步操作(例如 await 数据库查询、网络请求),那么 create_data 函数本身也应该是一个 async def 函数,并且在 lifespan 中调用时需要使用 await create_data()。
- 错误处理: 在 lifespan 的启动阶段(yield 之前)如果发生未捕获的异常,FastAPI 应用将无法启动。因此,建议对关键的初始化逻辑进行适当的错误处理,例如使用 try...except 块。
- 资源清理: yield 之后的代码块是执行资源清理的理想位置。确保在这里释放所有在启动阶段获取的资源,以避免内存泄漏或资源耗尽。
- 全局状态管理: 尽管在 lifespan 中修改全局变量是可行的,但对于更复杂的应用,考虑使用依赖注入(Dependency Injection)或 FastAPI 的 app.state 来管理应用级别的状态,以提高代码的可维护性和可测试性。app.state 允许你在应用实例上直接存储和访问状态。
总结
通过 FastAPI 的 lifespan 事件结合 asynccontextmanager,我们可以优雅且高效地管理应用的启动和关闭事件。这种机制确保了在服务正式对外提供前,所有必要的初始化工作都能顺利完成,并且在服务关闭时能够进行必要的资源清理。掌握 lifespan 是构建健壮和可维护的 FastAPI 应用的关键一环。










