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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:33:22