
本文探讨了在celery中处理动态创建子任务并确保其完成同步等待的挑战。针对celery链(chain)和弦(chord)无法在运行时动态添加依赖的局限性,文章提出并详细阐述了一种手动实现策略。该策略通过在父任务中收集动态子任务id,并使用循环轮询这些子任务的状态,直至所有子任务成功完成,从而实现精确的流程控制和数据完整性保障。
在构建复杂的分布式任务流时,我们经常遇到需要顺序执行一系列主任务,但在某个主任务内部,为了提高效率,又希望并行处理一些子任务的场景。例如,一个主任务可能需要通过API分批获取数据页,每获取一页数据后,就立即触发一个子任务来处理和写入数据库。由于数据库写入操作耗时较长且数量庞大,将其异步化为子任务可以显著减少主任务的整体墙钟时间。然而,关键挑战在于,下一个主任务必须等待所有这些动态创建的数据库写入子任务完成后才能继续执行,以确保数据完整性。
Celery提供了强大的任务编排工具,如chain、chord和group,用于定义任务之间的依赖关系和执行顺序。然而,这些工具的核心设计理念是基于预先定义的任务签名(signatures)。这意味着,在创建chain或chord时,所有参与的任务及其依赖关系都必须是已知的。
对于我们上述的场景,子任务是在主任务执行过程中动态生成的,其数量和具体签名无法在主任务启动前确定。在这种情况下,传统的Celery编排工具便显得力不从心:
简而言之,Celery的编排机制无法在任务被调度到Worker后,动态地修改其依赖关系或为其添加新的、运行时产生的子任务。任何阻塞等待逻辑都必须由任务本身显式地实现。
鉴于Celery原生编排工具的局限性,解决动态子任务同步等待问题的有效方法是手动实现一个轮询(polling)机制。这种策略的核心思想是:父任务在创建所有动态子任务后,收集这些子任务的ID,然后进入一个循环,周期性地检查每个子任务的状态,直到所有子任务都成功完成。
以下是实现这一策略的详细步骤和示例代码:
Sitekick
一个AI登陆页面自动构建器
121
查看详情
在父任务中,当需要创建子任务时,使用apply_async()方法调度它们,并务必将返回的AsyncResult对象的id属性收集到一个列表中。这个列表将用于后续的轮询。
import time
from typing import List
from celery import Celery, Task, AsyncResult
from celery.signals import task_postrun
# 假设的Celery应用实例
app = Celery('my_app', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0')
# 模拟的JobMaster和常量,用于日志记录
class JobMaster:
def get_job(self, job_id, job_title):
print(f"[JobMaster] Getting job {job_id} - {job_title}")
return self, job_id
def log_message(self, log_message, status=None, job_score=None):
print(f"[JobMaster] Log: {log_message} (Status: {status}, Score: {job_score})")
class Consts:
IN_PROGRESS = "IN_PROGRESS"
COMPLETED = "COMPLETED"
ERRORS_FOUND = "ERRORS_FOUND"
# 模拟的子任务
@app.task
def task_dummy_subtask(parent_task_name: str, job_id: int = None):
job, _ = JobMaster().get_job(job_id, job_title="dummy subtask")
job.log_message(log_message=f"Subtask {parent_task_name} started.")
time.sleep(2) # 模拟耗时操作
job.log_message(log_message=f"Subtask {parent_task_name} finished successfully.")
return f"Result from {parent_task_name}"
# 模拟的中间函数,用于创建子任务
def intermediary_dummy_subtask_function(parent_task_name: str, job_id: int = None) -> AsyncResult:
job, _ = JobMaster().get_job(job_id, job_title="dummy task")
job.log_message(log_message=f"Intermediary function for {parent_task_name} has been reached, will now make a task")
r = task_dummy_subtask.apply_async(kwargs={"parent_task_name": parent_task_name, "job_id": job_id},
add_to_parent=True)
return r
@app.task(bind=True)
def task_dummy_task1(self: Task, part_number: int, job_id: int = None):
job, job_id = JobMaster().get_job(job_id, job_title="dummy task")
sleeping_duration = 1
subtask_ids = []
job.log_message(log_message=f"Entered dummy task 1 with sleeping duration of {sleeping_duration}")
# 直接创建子任务
job.log_message(log_message="In dummy task1, creating subtask a")
subtask = task_dummy_subtask.apply_async(kwargs={"parent_task_name": "task1_a", "job_id": job_id},
add_to_parent=True)
subtask_ids.append(subtask.id)
job.log_message(log_message="In dummy task1, creating subtask b")
subtask = task_dummy_subtask.apply_async(kwargs={"parent_task_name": "task1_b", "job_id": job_id},
add_to_parent=True)
subtask_ids.append(subtask.id)
# 通过中间函数创建子任务
job.log_message(log_message="In dummy task1, creating intermediary subtask c")
subtask = intermediary_dummy_subtask_function(parent_task_name="task1_c", job_id=job_id)
subtask_ids.append(subtask.id)
time.sleep(sleeping_duration) # 模拟主任务的其他操作
# 等待所有子任务完成
wait_for_tasks_to_complete(async_ids=subtask_ids, job_id=job_id,
msg="Waiting in dummy task1 for subtasks to complete")
job.log_message(log_message="Finished dummy task1 main body")
return part_number创建一个辅助函数,如wait_for_tasks_to_complete,它接收子任务ID列表、日志ID和可选的超时时间。该函数将循环检查每个子任务的状态,直到所有子任务都完成或达到超时。
def wait_for_tasks_to_complete(async_ids: List[str], job_id: int = None, msg: str = None, timeout: int = 300):
job, _ = JobMaster().get_job(job_id, job_title="waiting for refresh data")
# 复制一份ID列表,因为在循环中会移除已完成的任务
remaining_async_ids = list(async_ids)
job.log_message(log_message=f"Waiting for {len(remaining_async_ids)} tasks to complete, {msg}", status=Consts.IN_PROGRESS, job_score=0)
job.log_message(log_message=f"tasks: {remaining_async_ids}", status=Consts.IN_PROGRESS, job_score=0)
count_down = timeout
while count_down > 0:
# 遍历剩余任务,检查其状态
tasks_to_check = list(remaining_async_ids) # 避免在迭代时修改列表
all_succeeded_in_this_check = True
for async_id in tasks_to_check:
result = app.AsyncResult(async_id) # 获取任务结果对象
status = result.status
if status == "SUCCESS":
returned_value = result.result
job.log_message(log_message=f"Task {async_id} confirmed status SUCCESS with {returned_value=}")
remaining_async_ids.remove(async_id) # 从待检查列表中移除
elif status in ["FAILURE", "REVOKED", "RETRY"]: # 考虑失败或撤销状态
job.log_message(log_message=f"Task {async_id} failed or revoked with status {status}. Aborting wait.", status=Consts.ERRORS_FOUND)
# 根据业务需求,可以选择在此处抛出异常或返回失败
return False
else:
all_succeeded_in_this_check = False # 仍有任务未完成或未成功
# 如果所有任务都已完成
if not remaining_async_ids:
job.log_message(log_message="Finished waiting for refresh data, all tasks succeeded",
status=Consts.COMPLETED, job_score=100)
return True # 所有任务成功完成
count_down -= 1
job.log_message(log_message=f"There are {len(remaining_async_ids)} tasks remaining. Waiting...", status=Consts.IN_PROGRESS)
time.sleep(1) # 避免忙等,每秒检查一次
# 超时退出
job.log_message(log_message=f"After waiting for {timeout=} seconds, some tasks did not complete on time. Remaining tasks: {remaining_async_ids}",
status=Consts.ERRORS_FOUND, job_score=100)
return False # 超时,未所有任务完成在父任务中,创建完所有动态子任务并进行其他必要操作后,调用上述wait_for_tasks_to_complete函数。父任务会在此处阻塞,直到所有子任务完成或超时。
# task_dummy_task1 的最后部分
# ...
time.sleep(sleeping_duration)
# 等待所有子任务完成
if wait_for_tasks_to_complete(async_ids=subtask_ids, job_id=job_id,
msg="Waiting in dummy task1 for subtasks to complete"):
job.log_message(log_message="Finished dummy task1 main body after all subtasks completed.")
else:
job.log_message(log_message="Dummy task1 finished with issues: subtasks did not complete on time or failed.", status=Consts.ERRORS_FOUND)
return part_number尽管Celery的内置编排工具在处理预定义任务流时表现出色,但面对运行时动态创建的子任务,它们存在固有的局限性。通过手动收集动态子任务ID并实现一个轮询等待循环,我们可以有效地解决这一挑战,确保父任务在所有相关子任务完成后才继续执行。这种手动策略虽然增加了代码的复杂性,但为需要精确流程控制和数据完整性的复杂异步任务场景提供了必要的灵活性和可靠性。在实施时,务必关注错误处理、超时管理和合理的轮询频率,以构建健壮的分布式系统。
以上就是Celery动态子任务的同步等待机制:突破链式限制的详细内容,更多请关注其它相关文章!
# app
# redis
# 会在
# 这一
# 链式
# elif
# red
# 异步任务
# ai
# 后端
# 工具
# seo文章投稿网站
# 开封整站seo关键词排名工具
# 音乐业务推广网站有哪些
# 木材网站建设内容
# 新店开张推广营销方案
# 邢台网站推广服务公司
# seo编程培训班
# 东昌府网站推广公司
# 淄博博山网站建设教程
# 起点网站建设文案素材
# 重试
# 如何实现
# 移除
# 至关重要
# 可以选择
# 将其
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
《宝可梦大集结》S4冠军之路开始时间介绍
使用TinyButStrong生成HTML并结合Dompdf创建PDF教程
抖音火山版如何进行提现
抖音评论无法发送如何修复 抖音评论功能操作指南
优化2xN网格最大路径和的动态规划算法实践
向日葵客户端怎么进行语音通话_向日葵客户端语音通话功能使用方法
邦丰播放器频道搜索设置
《真我》申请退款方法
酷狗音乐多音轨设置教程
蜻蜓FM如何设置移动流量播放
c++如何实现观察者设计模式_c++行为型设计模式实战
飞飞漫画漫画阅读官网_飞飞漫画漫画阅读官网进入阅读
感染了幽门螺杆菌一定会导致胃癌吗?蚂蚁庄园今日答案最新11.30
顺丰速运官网查询入口 顺丰物流查询官网入口链接
如何在Podman容器中运行Composer_Docker替代品Podman的PHP与Composer容器化实践
有道AI翻译入口 智能写作官方网站入口
智学网成绩单查询系统网_智学网学生平台登录
SQL聚合查询、联接与筛选:GROUP BY 子句的正确使用与常见陷阱
《异星探险家》古怪的物品作用介绍
微信如何设置字体大小_微信字体设置的阅读舒适
什么是Satis,如何用它搭建一个私有的composer仓库?
哔哩哔哩黑名单怎么查看
vivo云服务一直提示空间不足怎么办 怎么办vivo云服务老是提示空间不足
解决CSS容器溢出问题:使用calc()实现精确布局与边距控制
Python实战:高效处理实时数据流中的最小/最大值
《漫蛙manwa2》防走失网页版链接2025
百度地图离线地图无法加载如何解决 百度地图离线地图加载优化方法
在PHP环境中正确加载HTML资源:CSS样式与图片路径指南
J*aScript对象中深度嵌套URL键的查找与更新策略
菜鸟驿站的取件码忘了怎么办 手机快速查询指南
如何用mysql开发用户注册登录功能_mysql用户注册登录数据库设计
鲨鱼剧场app金币获取方法
126邮箱网页在线登录2025_126邮箱网页版入口官方地址
在VS Code中利用AI辅助进行代码迁移
QQ网页版入口导航 QQ网页版在线访问通道
解决CSS布局中意外顶部空白问题的教程
Go语言中方法接收器的选择:值类型还是指针类型?
谷歌浏览器如何查找和删除恶意软件 谷歌浏览器内置安全清理工具使用教程
Bootstrap 5导航栏折叠功能失效:数据属性迁移指南
性能与资源监视器快捷打开
word文档中的分隔符有哪些不同类型和用途_Word分隔符类型与用途方法
《爱笔思画x》涂色教程
windows10怎么开启wsl_windows10安装linux子系统教程
VS Code的时间线(Timeline)视图:您的代码时光机
冬季去哪个城市旅游更有可能观测到极光
汽水音乐在线入口 汽水音乐网页端官方页面快速打开
ExcelSCAN与LAMBDA如何创建自定义移动平均函数_SCAN实现任意窗口期移动平均计算
《百果园》充值余额方法
腾讯QQ邮箱官方入口 QQ邮箱网页版登录平台
掌握CSS :has() 选择器:父选择器、嵌套限制与常见陷阱解析
2025-12-02
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。