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

Celery Groups:如何创建包含已运行任务的任务组?

问题:Celery中如何创建包含已触发任务的任务组并自动协调后续流程

核心需求

  • 一批长时间运行的任务,每个任务创建完成后必须立即触发执行,不能等所有任务定义完毕再统一启动
  • 需要用任务组协调这批任务,全部完成后自动触发下游收尾任务
  • 当前卡点:group(...)不支持直接传入已通过apply_async触发的AsyncResult对象;若先构造group再调用apply_async,会导致任务重复执行两次

现有问题代码示例

import time
import numpy as np
from celery import group, chain, subtask
from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

# 启动入口
pipeline = rate_limited_task.s(end_task.s())
job_id_to_track = pipeline.apply_async()

# 任务定义
@app.task(bind=True)
def rate_limited_task(self, downstream_task):
    tasks = []
    # 模拟耗时的任务生成过程,循环多次
    for random_number in np.random.randint(1, 10, 1000):
        # 此处希望任务创建后立即执行,但当前写法要等循环结束才会通过group启动
        task = parallel_tasks.si(random_number) 
        tasks.append(task)

    # 构造组任务+下游任务的流水线,但此时所有任务才刚启动,不符合"立即触发"要求
    pipeline = group(tasks) | downstream_task
    return pipeline()

@app.task(bind=True)
def parallel_tasks(self, data):
    print(f'sleeping for {data} seconds')
    time.sleep(data)
    return data

@app.task(bind=True)
def end_task(self, results):
    print('End task')
    print(results)

现有非理想解决方案(存在缺陷)

import uuid
import numpy as np
from celery import Celery, chain
from celery.result import allow_join_result, GroupResult

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True)
def rate_limited_task(self, downstream_task):
    tasks = []
    # 循环触发任务,每个任务创建后立即执行
    for random_number in np.random.randint(1, 10, 1000):
        task = parallel_tasks.apply_async([random_number])
        tasks.append(task)

    # 手动创建GroupResult并保存
    gid = uuid.uuid4()
    result_set = GroupResult(id=gid, results=tasks)
    result_set.save(backend=self.backend)

    # 启动监控任务,等待所有任务完成后触发下游
    chain(
        group_tasks.s(gid),
        downstream_task
    )()

@app.task(bind=True)
def group_tasks(self, group_task_id):
    group_task = GroupResult.restore(group_task_id, app=app, backend=self.backend)

    # 缺陷点:需自行实现任务就绪检查、失败重试逻辑,无法复用Celery原生group的成熟机制
    if not group_task.ready():
        raise self.retry(countdown=1)

    with allow_join_result():
        results = group_task.join()
        return results

@app.task(bind=True)
def parallel_tasks(self, data):
    print(f'sleeping for {data} seconds')
    time.sleep(data)
    return data

@app.task(bind=True)
def end_task(self, results):
    print('End task')
    print(results)

该方案的明显缺陷:

  • 需要手动实现任务就绪轮询、失败重试逻辑,无法复用Celery原生group的错误处理、重试机制
  • 缺少任务过期控制,可能导致监控任务无限重试
  • 代码冗余,不符合Celery原生设计规范

最优解决方案:利用Celery的chord+动态任务提交

通过提前初始化chord的回调任务,再逐个提交任务到chord的任务组,既满足任务创建后立即执行的需求,又能复用Celery原生的组任务协调、失败处理和过期机制:

import time
import numpy as np
from celery import Celery, chord
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)
app = Celery('tasks', broker='redis://localhost:6379/0')

# 启动入口:先初始化chord回调,再触发任务生成器
@app.task(bind=True)
def start_pipeline(self):
    # 提前定义chord的下游收尾任务
    callback = end_task.s()
    # 初始化空的chord任务组,后续动态添加任务
    chord_group = chord((), callback)
    # 获取chord的组ID,用于后续动态提交任务
    gid = chord_group.group_id
    # 触发任务生成任务,传入组ID
    rate_limited_task.si(gid).apply_async()
    return gid

@app.task(bind=True)
def rate_limited_task(self, group_id):
    # 循环生成并立即提交任务到指定的chord组
    for random_number in np.random.randint(1, 10, 1000):
        # 模拟耗时的任务生成过程
        logger.info(f"生成任务,休眠时长:{random_number}")
        # 直接将任务提交到已初始化的chord组,任务创建后立即执行
        app.send_task(
            'tasks.parallel_tasks',
            args=[random_number],
            group_id=group_id,
            # 复用Celery原生过期机制,设置任务过期时间
            expires=3600
        )

@app.task(bind=True)
def parallel_tasks(self, data):
    logger.info(f'休眠 {data} 秒')
    time.sleep(data)
    return data

@app.task(bind=True)
def end_task(self, results):
    logger.info('收尾任务完成')
    logger.info(f"总结果数:{len(results)}")
    return results

方案说明

  1. 核心原理:Celery的chord允许先初始化空任务组,再通过send_task指定group_id,将任务动态添加到组中,每个任务提交后立即执行
  2. 核心优势:
    • 完全满足"任务创建后立即触发"的需求
    • 复用Celery原生group/chord的失败处理、重试、过期机制,无需手动实现
    • 代码简洁,符合Celery设计规范
  3. 注意事项:
    • 确保Celery结果后端支持GroupResult存储(如Redis、RabbitMQ+数据库等)
    • send_task指定的任务名称需与实际任务注册名称一致(示例中为'tasks.parallel_tasks')
    • 可根据需求为任务设置expires、retry等参数,直接复用Celery原生能力

内容的提问来源于stack exchange,提问作者X_Trust

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 17:54:56