FastAPI 和 aiohttp 是否共享同一个全局事件循环?
在异步编程场景中,事件循环是调度和执行异步任务的核心组件。很多开发者在使用 FastAPI 和 aiohttp 时会疑惑:这两者是否会共享同一个全局事件循环?要解答这个问题,我们需要先了解两者的事件循环管理机制,再通过实际代码验证。
事件循环的基本管理机制
Python 的异步编程基于 asyncio 库,默认情况下,asyncio 并没有所谓的“全局事件循环”概念。每个线程可以有自己的事件循环,而 asyncio.get_event_loop() 获取的是当前线程中已设置的事件循环,如果没有设置,则会根据运行环境创建新的事件循环。
FastAPI 是基于 Starlette 框架构建的异步 Web 框架,其底层使用 ASGI 服务器(如 uvicorn、hypercorn)运行,这些服务器会自行创建和管理事件循环,通常不会依赖外部的全局事件循环。
aiohttp 是异步 HTTP 客户端/服务器框架,它的客户端和服务器组件也都会独立创建和管理自己的事件循环,除非显式将已有的事件循环传入使用。
实际验证:分别获取两者的事件循环
我们可以通过一个简单的 FastAPI 接口,在其中调用 aiohttp 请求,同时打印两者的事件循环对象,来验证是否为同一个。
import asyncio
from fastapi import FastAPI
import aiohttp
app = FastAPI()
@app.get("/test-loop")
async def test_loop():
# 获取当前 FastAPI 接口运行所在的事件循环
fastapi_loop = asyncio.get_event_loop()
# 在 aiohttp 客户端请求中获取事件循环
aiohttp_loop = None
async with aiohttp.ClientSession() as session:
# 发起一个简单的请求,在请求过程中获取事件循环
async with session.get("http://ipipp.com") as response:
# 在 aiohttp 的请求回调中获取当前事件循环
aiohttp_loop = asyncio.get_event_loop()
# 对比两个事件循环是否为同一个对象
is_same = fastapi_loop is aiohttp_loop
return {
"fastapi_loop_id": id(fastapi_loop),
"aiohttp_loop_id": id(aiohttp_loop),
"is_same_loop": is_same
}运行上述 FastAPI 应用后,访问 /test-loop 接口,返回结果中 is_same_loop 会显示为 True。这是因为当我们在同一个 coroutine 中调用 aiohttp 时,两者运行在同一个事件循环上下文中,asyncio.get_event_loop() 获取到的是当前协程所在的同一个事件循环。
特殊情况:跨线程或显式创建新循环
如果我们在 FastAPI 应用中单独启动一个新线程运行 aiohttp 相关逻辑,并且在新线程中没有显式设置事件循环,那么两者就不会共享同一个事件循环。我们可以通过以下代码验证这种场景:
import asyncio
from fastapi import FastAPI
import aiohttp
import threading
app = FastAPI()
def run_aiohttp_in_new_thread():
# 新线程中默认没有事件循环,需要手动创建并设置
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
async def aiohttp_task():
async with aiohttp.ClientSession() as session:
async with session.get("http://ipipp.com") as response:
current_loop = asyncio.get_event_loop()
print(f"新线程中的事件循环id: {id(current_loop)}")
loop.stop()
loop.run_until_complete(aiohttp_task())
@app.get("/test-thread-loop")
async def test_thread_loop():
# 获取 FastAPI 所在的事件循环
fastapi_loop = asyncio.get_event_loop()
print(f"FastAPI 事件循环id: {id(fastapi_loop)}")
# 启动新线程运行 aiohttp 逻辑
thread = threading.Thread(target=run_aiohttp_in_new_thread)
thread.start()
thread.join()
return {"message": "请查看控制台输出的事件循环id对比结果"}运行上述代码后,控制台会输出两个不同的事件循环 id,说明这种情况下 FastAPI 和 aiohttp 使用的是不同的事件循环。
结论
FastAPI 和 aiohttp 本身并没有固定的“共享全局事件循环”的规则,是否共享取决于使用场景:
- 如果在同一个线程的异步上下文中(也就是同一个协程链中)使用两者,它们会共享同一个事件循环,因为 asyncio.get_event_loop() 获取的是当前线程上下文中的事件循环。
- 如果跨线程使用,或者在 aiohttp 中显式创建了新的事件循环,那么两者会使用不同的事件循环。
实际开发中,建议在同一个异步上下文中使用 FastAPI 和 aiohttp,避免手动创建多个事件循环,减少不必要的复杂度。如果需要同时使用两者,直接在正常编写的异步接口中调用 aiohttp 方法即可,无需额外处理事件循环相关逻辑。