BackgroundTasks 如何巧妙驾驭多任务并发?

一、BackgroundTasks 工作原理与使用场景

graph TD A[用户请求] --> B[路由处理函数] B --> C{是否包含BackgroundTasks} C -- 是 --> D[将任务加入队列] C -- 否 --> E[直接返回响应] D --> F[返回响应] F --> G[后台执行队列任务] G --> H[任务执行完成]

二、多任务并发控制策略

2.1 异步任务队列实现

python 复制代码
from fastapi import BackgroundTasks, FastAPI
from pydantic import BaseModel
import asyncio

app = FastAPI()

class TaskRequest(BaseModel):
    data: str
    priority: int = 1

async def process_task(task: TaskRequest):
    # 模拟耗时操作
    await asyncio.sleep(2)
    print(f"Processed: {task.data}")

@app.post("/submit-task")
async def submit_task(
    request: TaskRequest,
    background_tasks: BackgroundTasks
):
    semaphore = asyncio.Semaphore(5)  # 最大并发数控制
    background_tasks.add_task(
        run_with_concurrency_control,
        semaphore,
        process_task,
        request
    )
    return {"status": "Task queued"}

async def run_with_concurrency_control(semaphore, func, *args):
    async with semaphore:
        return await func(*args)

所需依赖:

ini 复制代码
fastapi==0.68.0
pydantic==1.10.7
uvicorn==0.15.0

2.2 优先级任务调度

通过装饰器实现优先级队列:

python 复制代码
from collections import deque

class PriorityQueue:
    def __init__(self):
        self.high = deque()
        self.normal = deque()

    def add_task(self, task, priority=1):
        if priority > 1:
            self.high.append(task)
        else:
            self.normal.append(task)

    def get_next(self):
        return self.high.popleft() if self.high else self.normal.popleft()

三、课后 Quiz

问题 1

当需要处理耗时 10 分钟以上的任务时,应该选择 BackgroundTasks 还是 Celery?

答案解析: BackgroundTasks 适合短时任务(<5分钟),长时间任务建议使用 Celery:

  1. BackgroundTasks 依赖请求生命周期
  2. 进程重启会导致任务丢失
  3. 缺乏分布式任务追踪能力

问题 2

以下哪种方式可以有效防止并发任务数超过系统负载? A) 使用线程池 B) 设置 Semaphore C) 增加服务器数量 D) 使用数据库锁

正确答案:B

解析:Semaphore 是控制并发的原生机制,可在应用层直接限制并行任务数

四、常见报错处理

4.1 422 Validation Error

现象

json 复制代码
{
    "detail": [
        {
            "loc": ["body", "priority"],
            "msg": "field required",
            "type": "value_error.missing"
        }
    ]
}

解决方案

  1. 检查 Pydantic 模型定义是否缺少 required 字段
  2. 使用 Optional 类型标记可选参数:
python 复制代码
from typing import Optional

class TaskRequest(BaseModel):
    data: str
    priority: Optional[int] = 1  # 默认值设为1

4.2 后台任务未执行

可能原因

  1. 未正确传递 background_tasks 参数
  2. 任务函数未使用 async 定义
  3. 请求提前中断导致任务队列未执行

排查步骤

python 复制代码
@app.post("/debug-task")
async def debug_task(
    background_tasks: BackgroundTasks
):
    def sync_task():
        print("Debug task executed")
    
    background_tasks.add_task(sync_task)
    return {"status": "Debug task queued"}

检查控制台输出确认任务执行情况

4.3 并发超限错误

现象 :RuntimeError: Too many concurrent tasks 预防措施

  1. 在应用启动时初始化全局信号量
  2. 结合队列系统实现流量削峰
python 复制代码
@app.on_event("startup")
async def init_concurrency_control():
    app.state.task_semaphore = asyncio.Semaphore(10)
相关推荐
勇哥java实战分享8 小时前
PaddleOCR 太慢?我换成 RapidOCR 后,速度直接起飞
后端
苏三说技术12 小时前
LangChain4j 和 LangGraph4j,哪个更好?
后端
ServBay13 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
妙码生花14 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
用户67570498850214 小时前
Go 语言里判断字符串为空,90% 的人都写错了!
后端·go
用户67570498850214 小时前
Go 进阶必修:90% 的人都没用对的“表驱动法”
后端·go
小兔崽子去哪了14 小时前
Java 生成二维码解决方案
java·后端
苍何14 小时前
懂事的 Agent 已经开始自己看屏幕干活了,效率起飞!
后端
掘金码甲哥15 小时前
1分钟买不了吃亏系列: nginx动态域名解析
后端