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)
相关推荐
码事漫谈11 分钟前
腾讯混元 Hy4 正式版发布了
后端
Mikko712 分钟前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
莫晓燚1 小时前
Ansible 13 个常用模块---企业实战
git·github
苍何2 小时前
DeepSeek的Super-agent 设计,和我们的工程实践
后端
Patrick_Wilson2 小时前
第一次读 Go 项目:从两个源码文件弄清 package、import 和 go.mod
后端·go·编程语言
Patrick_Wilson2 小时前
执行 go build . 后,Go 怎样找到代码并生成程序
后端·go·编程语言
用户6919026813392 小时前
Redis 2:Redis 在怎么处理harness记忆
后端
Patrick_Wilson2 小时前
Go 变量不赋值时,为什么也有值
后端·go·编程语言
Patrick_Wilson2 小时前
读 Go 项目里的命令:run、build、test 分别做了什么
后端·go·编程语言
程序员柒叔3 小时前
Diagram Design 解构:45 种图型如何用语义路由与 SVG 质量门交付
人工智能·github·agent·skills