BackgroundTasks.add_task()不脱离事件循环,任务阻塞会导致请求超时、并发排队;无重试/状态追踪,进程重启即丢失;Celery通过独立worker和消息队列实现真异步、重试、监控与水平扩展。

BackgroundTasks.add_task() 会拖慢整个请求生命周期
它不是独立后台线程或进程,只是把函数注册到当前请求响应发出后、连接关闭前执行。一旦任务耗时(比如 time.sleep(10) 或 SMTP 超时),就会卡住这个请求上下文——Uvicorn 可能因此触发超时,返回 504;更糟的是,如果并发多个这种任务,事件循环会被阻塞,其他请求全得排队等。
常见错误现象:
- 接口返回快,但日志里发现“任务开始时间”比响应时间晚好几秒
- 用
curl -v看到连接 hang 在Waiting for response阶段 - 加了
print()发现后台函数实际执行远滞后于 HTTP 响应
BackgroundTasks 没有重试、超时、状态追踪能力
它纯内存队列:进程重启即清空,失败不报错,也不记录是否执行过。你调用 background_tasks.add_task(send_email, user_id),如果邮件服务器连不上,函数抛异常,FastAPI 就静默吞掉——你根本不知道发没发成功。
关键限制点:
立即学习“Python免费学习笔记(深入)”;
- 无法设置
timeout=30参数控制单次执行上限 - 没有任务 ID、没有
.status()、不能查“这个用户的通知发了几条” - 没法区分“已入队”“正在跑”“失败重试中”“已成功”
- 数据库写一半失败,邮件却发出去了,数据状态就错乱
Celery 才是生产环境处理耗时任务的合理选择
它把任务推到消息队列(如 Redis/RabbitMQ),由独立的 celery worker 进程消费执行。这意味着:任务脱离请求生命周期、支持失败自动重试、可监控进度、能水平扩容 worker 数量、支持定时调度(@app.task(bind=True, autoretry_for=(Exception,), retry_kwargs={"max_retries": 3}))。
典型适配场景:
- 发邮件、短信、推送通知(需重试 + 超时)
- 导出大 Excel、压缩视频、调用 AI 接口(耗时 >2s,且不能阻塞主线程)
- 用户注册后批量同步数据到 CRM(多步骤、需事务一致性)
- 每小时跑一次库存校验(定时 + 持久化,不怕进程重启)
注意:Celery worker 必须和 FastAPI 应用分开启动,不能塞在同一个 uvicorn 进程里;也别用 celery -A tasks worker --loglevel=info 直接在开发机前台跑,要配合 systemd 或 Docker Compose 管理生命周期。
别用 BackgroundTasks 做异步数据库操作
很多人以为“丢进 background_tasks 就不会阻塞”,结果在后台函数里直接用了同步 ORM 查询(比如 session.execute(...).fetchall()),照样卡事件循环。因为 SQLAlchemy 默认是同步驱动,哪怕你在 add_task() 里调,它还是在主线程里跑阻塞 I/O。
正确做法只有两个:
- 换异步驱动(如
asyncpg+SQLModel),后台函数写成async def,用await db.executemany() - 或者老实用 Celery:worker 进程天然隔离,同步 ORM 安全,还能开多进程并行跑
最常被忽略的一点:BackgroundTasks 的“后台”是假后台,它根本没脱离 FastAPI 的事件循环上下文;而 Celery 的 worker 是真进程,有自己的内存、线程、数据库连接池——这才是应对耗时任务的物理基础。


















