FastAPI/Starlette中Websocket场景下Background Task无法执行的解决
问题分析与解决方案
问题根源
FastAPI的BackgroundTasks机制是在请求处理函数完全执行结束后才会调度后台任务运行,但你的WebSocket端点进入了while True的无限循环,一直阻塞着函数流程,导致后台任务根本没有启动的机会。
修复方案
改用asyncio.create_task直接启动异步任务,它会立刻在事件循环中后台运行,不会等待主函数结束。同时要确保Redis PubSub客户端是异步版本(比如redis.asyncio),避免阻塞事件循环。
修改后的代码
import asyncio import aiohttp from fastapi import WebSocket, FastAPI app = FastAPI() STOPWORD = "STOP" async def get_redis_pubsub(): # 确保这里返回的是异步Redis PubSub客户端 import redis.asyncio as redis r = redis.Redis(host='localhost', port=6379, db=0) return r.pubsub() async def call_external_server(channel, text): print("call_external_server start") async with aiohttp.ClientSession() as session: # 替换直接拼接URL为params参数,避免注入风险 async with session.get( "http://localhost:9000/pub", params={"channel": channel, "text": text} ) as resp: print(f"外部服务器响应状态:{resp.status}") print("call_external_server finished") return {"response": "external_server is done"} @app.websocket("/ws/{channel}") async def websocket_endpoint(channel: str, websocket: WebSocket): await websocket.accept() client_info = dict(websocket.headers) text = client_info.get("text") redis_reader = await get_redis_pubsub() await redis_reader.subscribe(channel) # 替换BackgroundTasks为asyncio.create_task,立即启动后台任务 asyncio.create_task(call_external_server(channel, text)) try: while True: message = await redis_reader.get_message(ignore_subscribe_messages=True) if message is not None: decoded_msg = message["data"].decode() if decoded_msg == STOPWORD: print("(Reader) STOP") break await websocket.send_text(decoded_msg) # 让出事件循环时间,避免阻塞其他任务 await asyncio.sleep(0.01) except Exception as e: print(f"发生异常:{e}") await websocket.close() return await websocket.close()
额外注意事项
- 原代码直接拼接URL参数存在安全风险,改用
params参数传递更规范安全。 - 在
while True循环中加入await asyncio.sleep(0.01),让出事件循环时间给其他异步任务,避免出现任务饥饿问题。 - 必须使用异步Redis客户端(
redis.asyncio),否则await调用会阻塞整个事件循环,导致后台任务无法正常执行。
内容的提问来源于stack exchange,提问作者Care
相关产品推荐
相关产品推荐

