You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI限制外部请求遇事件循环错误,需实现串行调用

问题:FastAPI使用asyncio.Semaphore限制并发时出现跨事件循环错误

我编写了如下FastAPI代码,尝试通过asyncio.Semaphore限制外部API的并发请求数:

from fastapi import APIRouter, Depends
import aiohttp
import asyncio

router = APIRouter()
semaphore = asyncio.Semaphore(1)

async def request_from_external_api(session):
    async with session.get("some external api endpoint") as response:
        return await response.text()

@router.post("/internal_request_1")
async def internal_request_1():
    async with semaphore:   # 限制并发请求数
        async with aiohttp.ClientSession() as session:
            return await request_from_external_api(session)
        
@router.post("/internal_request_2")
async def internal_request_2():
    async with semaphore:   # 限制并发请求数
        async with aiohttp.ClientSession() as session:
            return await request_from_external_api(session)

测试同时发起2个请求时,触发如下错误:

RuntimeError: Task <Task pending coro=<RequestResponseCycle.run_asgi() running at C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\uvicorn\protocols\http\httptools_impl.py:420> cb=[set.discard()]> got Future attached to a different loop

需要实现FastAPI应用逐个串行调用request_from_external_api(),解决该问题。


问题原因

模块初始化时创建的asyncio.Semaphore绑定了当时的事件循环,但FastAPI(Uvicorn)启动后会创建新的事件循环,导致Semaphore与后续请求任务不在同一循环中,引发跨循环错误。此外,每次请求新建aiohttp.ClientSession既不高效也可能加剧循环问题。

解决方案

1. 延迟创建Semaphore,绑定到运行时事件循环

通过FastAPI的lifespan上下文管理器,在应用启动时获取当前运行的事件循环,再创建Semaphore,确保其与请求任务处于同一循环。

2. 全局复用aiohttp.ClientSession

遵循aiohttp最佳实践,全局创建一个ClientSession实例,避免每次请求新建,提升性能同时避免循环相关问题。

修改后的代码

from fastapi import APIRouter, FastAPI
import aiohttp
import asyncio
from contextlib import asynccontextmanager

router = APIRouter()
semaphore = None
session = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global semaphore, session
    # 绑定到应用运行时的事件循环创建Semaphore
    semaphore = asyncio.Semaphore(1)
    # 初始化全局aiohttp ClientSession
    session = aiohttp.ClientSession()
    yield
    # 应用关闭时清理资源
    await session.close()

async def request_from_external_api():
    async with session.get("some external api endpoint") as response:
        return await response.text()

@router.post("/internal_request_1")
async def internal_request_1():
    async with semaphore:
        return await request_from_external_api()
        
@router.post("/internal_request_2")
async def internal_request_2():
    async with semaphore:
        return await request_from_external_api()

# 初始化应用并绑定lifespan
app = FastAPI(lifespan=lifespan)
app.include_router(router)

说明

  • lifespan确保Semaphore和ClientSession在应用启动时创建,绑定到正确的事件循环,避免跨循环错误。
  • 全局复用ClientSession减少资源开销,符合异步HTTP客户端的使用规范。
  • Semaphore的async with语法确保外部API请求被串行处理,满足并发限制需求。

内容的提问来源于stack exchange,提问作者Luciano Garbarino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 11:27:49