Django 6.0 把 Tasks 框架正式纳入核心后,很多开发者第一反应都是:既然已经有 @task、enqueue()、队列名、优先级和结果对象,项目里是不是终于可以不再接 Celery 了?这个问题不能只看发布说明,因为“有统一接口”和“能在生产环境稳定跑后台任务”根本不是一回事。
为了把边界看清楚,本文按同一组 1.5 秒耗时任务分别测试了三种方案:普通同步函数、Django 自带 ImmediateBackend/DummyBackend,以及 Redis + 独立 Celery Worker。看完你可以直接判断:Django Tasks 到底解决了什么、没解决什么,以及新老项目该怎么选型。
Django 6.0 到底内置了什么
Django 官方对 Tasks 的定位非常克制:它提供的是后台工作的“contract and plumbing”,也就是任务契约和连接管道,真正负责搬运、调度和执行任务的引擎,仍然交给外部基础设施。
一个最小任务可以这样定义:
from django.tasks import task
@task
def generate_report(report_id):
# 生成报表
return {"report_id": report_id, "status": "done"}
调用方式也不是 Celery 常见的 delay(),而是 Django 自己的 enqueue():
result = generate_report.enqueue(42)
这套 API 还统一了几类关键概念:
priority:任务优先级queue_name:目标队列backend:使用哪个任务后端run_after:最早执行时间TaskResult:任务状态、尝试次数、错误与返回值enqueue()/aenqueue():同步与异步提交接口
这件事本身很有价值。过去每个任务库都有自己的装饰器、调用方式和结果对象;从 Django 6.0 开始,业务代码至少可以先围绕统一接口组织,再把具体执行能力交给后端实现。
但要注意,统一接口并不等于任务已经被真正移出 Web 请求进程。
两个内置 Backend 为什么都不能当生产 Worker
Django 6.0 自带了两个后端,但它们都不等于“生产可用的后台任务系统”。
ImmediateBackend:调用了 enqueue,任务还是在当前进程执行
默认配置就是 ImmediateBackend:
TASKS = {
"default": {
"BACKEND": "django.tasks.backends.immediate.ImmediateBackend",
}
}
它的行为很直接:调用 enqueue() 时立即执行任务函数。如果任务要跑 1.5 秒,请求就照样等待 1.5 秒,和真正的异步执行不是一回事。
它更适合这些场景:
- 本地开发时先把业务调用改成 Tasks API
- 单元测试中验证任务逻辑
- 在正式队列设施尚未接入前,先完成接口迁移
它并不能把耗时工作移出 Web 进程。
DummyBackend:返回很快,但它根本不执行任务
DummyBackend 的配置如下:
TASKS = {
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
}
}
它会记录入队行为,方便测试断言,但不会真正执行任务函数。所以如果它在 1 ms 内返回,并不能说明“Django 后台任务性能很强”,只能说明它只做了记录,任务状态会停留在 READY。
官方文档写得很明确:Django 内置的只是开发和测试后端,生产可用后端需要额外配置。
测试环境、执行步骤与五轮结果
为了把差异跑实,测试环境固定如下:
| 项目 | 配置 |
|---|---|
| Python | 3.12 |
| Django | 6.0.7 |
| Celery | 5.6.3 |
| Broker | Redis,本地独立数据库编号 |
| Worker | 单独进程,--pool=solo |
| 任务内容 | sleep(1.5),模拟邮件、报表或文件处理 |
| 轮数 | 5 轮 |
测试任务会返回执行进程 PID,用来确认它到底是仍在请求进程里运行,还是已经被独立 Worker 接走。
第一组:普通同步函数
def slow_job(delay_seconds):
time.sleep(delay_seconds)
return {"worker_pid": os.getpid()}
这一组没什么悬念:直接调用时,请求进程必须完整等待任务结束。
第二组:Django ImmediateBackend 与 DummyBackend
from django.tasks import task
@task
def django_slow_job(delay_seconds):
return slow_job(delay_seconds)
immediate_result = django_slow_job.enqueue(1.5)
dummy_result = django_slow_job.using(backend="dummy").enqueue(1.5)
这里的关键区别只有一句话:ImmediateBackend 会真的执行,DummyBackend 只做记录。
第三组:Redis + 独立 Celery Worker
Celery 任务定义如下:
from celery import Celery
app = Celery(
"django_tasks_demo",
broker="redis://127.0.0.1:6379/13",
backend="redis://127.0.0.1:6379/14",
)
@app.task
def celery_slow_job(delay_seconds):
time.sleep(delay_seconds)
return {"worker_pid": os.getpid()}
启动独立 Worker:
python -m celery -A celery_app:app worker `
--pool=solo `
--queues=ruyi_django_tasks_demo
提交任务并记录入队耗时:
started = time.perf_counter()
result = celery_slow_job.delay(1.5)
enqueue_ms = (time.perf_counter() - started) * 1000
实验里调用 result.get(),只是为了确认 Worker 最终确实执行完成并统计完整耗时。真实 Web 请求里通常不应该在提交后立刻 get(),否则异步流程又会被你自己等回同步。
五轮结果说明了什么
| 方案 | 平均返回时间 | 最小值 | 最大值 | 是否执行 | 执行进程 |
|---|---|---|---|---|---|
| 同步函数 | 1500.29 ms | 1500.18 ms | 1500.54 ms | 是 | 请求进程 PID 42268 |
Django ImmediateBackend | 1500.66 ms | 1500.39 ms | 1501.24 ms | 是 | 请求进程 PID 42268 |
Django DummyBackend | 0.31 ms | 0.16 ms | 0.84 ms | 否 | 无 |
| Celery 入队返回 | 13.66 ms | 0.68 ms | 65.18 ms | 是 | Worker PID 37412 |
Celery 任务的平均完整完成时间是 1515.71 ms。任务本身并没有被“加速”,仍然需要大约 1.5 秒;真正变化的是,请求线程只负责把消息送进队列,实际执行改由另一个进程承担。
这正是后台任务最核心的价值:不是让工作凭空更快,而是让 Web 请求不用陪着耗时任务一起阻塞。
还有一个细节值得单独看待:Celery 首轮入队时间偏高,主要来自首次建立 Broker 连接的成本,后续轮次最低降到了 0.68 ms。所以单次冷启动结果不能直接代表长期吞吐,更不能拿本机样本直接当作生产性能承诺。
Django Tasks 和 Celery 的能力边界,到底差在哪
| 能力 | Django 6.0 Tasks 核心 | Celery |
|---|---|---|
| 统一任务声明与入队接口 | 是 | 有自己的 API |
| 自带生产 Worker | 否 | 是 |
| 自带生产消息传输 | 否 | 支持 Redis、RabbitMQ 等 Broker |
| 重试、路由、并发控制 | 取决于外部 Backend | 成熟支持 |
| 定时与复杂工作流 | 取决于外部 Backend | Celery Beat、Canvas 等生态 |
| 测试时立即执行或只记录 | 内置支持 | 可用 eager 等配置 |
| 结果监控与运维工具 | 取决于 Backend | 有成熟生态 |
所以“Django Tasks 能不能替代 Celery”,最好拆成两个问题来看:

- 能不能替代 Celery 的业务调用接口?可以朝这个方向走。只要使用兼容的生产 Backend,业务层确实可以围绕 Django 的
@task和enqueue()来写。 - 能不能替代 Celery 的队列、Worker、重试和运维体系?Django 核心本身做不到。
新项目和老项目,应该怎么选
适合优先采用 Django Tasks + 生产 Backend 的情况
- 新建 Django 6.0 项目,希望先统一任务接口
- 任务模型相对简单,以发邮件、生成缩略图、轻量数据处理为主
- 已经验证某个外部 Backend 能满足延迟、优先级、结果查询和异步执行能力
- 希望未来替换任务实现时,尽量少改业务层代码
选型时不要只看 Backend 能不能装上。Django Tasks 为后端预留了一组能力标志,比如 supports_defer、supports_priority、supports_get_result、supports_async_task,真正落地前最好逐项核实。

适合继续使用 Celery 的情况
- 现有系统已经稳定运行在 Celery 之上
- 需要任务重试、限流、路由、多队列、定时任务或复杂工作流
- 需要多机部署或多类 Worker 分工
- 已经有 Redis/RabbitMQ、监控和部署体系
- 团队已经积累了 Celery 的运维和故障处理经验
仅仅因为 Django 6.0 新增了 Tasks,就把成熟 Celery 体系整体重写,通常没有现实收益。
三个最容易踩的坑
坑一:把 enqueue() 误认为一定异步
enqueue() 的真实执行方式由 Backend 决定。默认的 ImmediateBackend 仍然会阻塞当前调用者。
坑二:测试返回很快,就误判生产也没问题
DummyBackend 不执行任务,ImmediateBackend 不离开当前进程。两者都不能证明生产队列链路已经可用。
坑三:事务还没提交,Worker 已经开始读数据
比如创建订单后立刻投递邮件任务,Worker 可能先于数据库事务提交去读取订单。更稳妥的通用做法,是在事务提交成功后再入队:
from django.db import transaction
transaction.on_commit(lambda: generate_report.enqueue(report_id))
Celery 的 Django 集成也提供了 delay_on_commit() 来处理这个场景。任务参数最好传主键这类稳定标识,让 Worker 执行时重新读取最新数据,而不是直接序列化整个模型对象。
Windows 实验为什么只能说明链路,不等于生产建议
这次实验为了在本机完成可重复验证,Celery Worker 使用了 --pool=solo。Celery 官方目前并不承诺 Windows 支持,这个配置适合演示独立进程和消息队列链路,但不能直接等同于生产部署建议。
真正上线时,通常还是应该在 Linux 环境下,根据任务类型评估 Worker 并发模型,并配合正式维护的 Redis 或 RabbitMQ 服务使用。
结论:Django Tasks 统一了接口,但没替你实现后台执行系统
Django 6.0 Tasks 最重要的意义,不是“Django 把 Celery 做进核心了”,而是 Django 终于提供了一套框架级的任务语言:
- 用
@task声明任务 - 用
enqueue()提交任务 - 用 Backend 隔离具体执行设施
- 用统一的结果与能力模型描述任务状态
但协议不会自己执行任务。没有生产 Backend、Broker 和 Worker,耗时工作仍然不会真正离开请求进程。
结论可以说得很直白:Django Tasks 的价值,在于降低业务代码和具体任务库之间的耦合;但它并不是 Celery 的完整替代品。 对已经有复杂异步架构的项目,继续使用 Celery 仍然是更稳妥的选择;而对于新建的 Django 6.0 项目,优先采用 Tasks API,再谨慎挑选生产后端,才是更现实的路线。
参考资料
- Django 6.0 Tasks API 官方文档
- Django Tasks 使用指南
- Celery 5.6 官方入门文档
- Celery 与 Django 官方集成指南
- Celery 并发模型文档









