我的问题可能很基本,但我仍然无法在官方文档中找到解决方案。我在Django应用程序中定义了一个Celery链,执行一组依赖于eanch其他的任务:
chain( tasks.apply_fetching_decision.s(x, y),
tasks.retrieve_public_info.s(z, x, y),
tasks.public_adapter.s())()
显然第二个和第三个任务需要父母的输出,这就是我使用链的原因。
现在的问题是:如果第一个任务中的测试条件失败,我需要以编程方式撤销第二个和第三个任务。如何以干净的方式做到这一点?我知道我可以从我定义链的方法中撤销链的任务(参见this问题和this doc)但里面我没有的第一个任务后续任务的可见性以及链本身的可见性。
我目前的解决方案是根据上一个任务的结果跳过后续任务中的计算:
@shared_task
def retrieve_public_info(result, x, y):
if not result:
return []
...
@shared_task
def public_adapter(result, z, x, y):
for r in result:
...
但是这种“解决方法”有一些缺陷:
由于担心搞乱事情,我没有过多地将链条的引用传递给任务。我也承认我还没有尝试过抛出异常的方法,因为我认为不进行链接的选择可能是一个功能性(因此非特殊情况)......
感谢您的帮助!
答案 0 :(得分:10)
我想我找到了 这个问题的答案: this 似乎是正确的方法。我想知道为什么在任何地方都没有记录这种常见情况。
为了完整性,我发布了基本代码快照:
@app.task(bind=True) # Note that we need bind=True for self to work
def task1(self, other_args):
#do_stuff
if end_chain:
self.request.callbacks[:] = []
....
我实施了一种更优雅的方式来解决这个问题,我希望与您分享。我正在使用一个名为revoke_chain_authority
的装饰器,这样它就可以自动撤销链而不重写我之前描述的代码。
from functools import wraps
class RevokeChainRequested(Exception):
def __init__(self, return_value):
Exception.__init__(self, "")
# Now for your custom code...
self.return_value = return_value
def revoke_chain_authority(a_shared_task):
"""
@see: https://gist.github.com/bloudermilk/2173940
@param a_shared_task: a @shared_task(bind=True) celery function.
@return:
"""
@wraps(a_shared_task)
def inner(self, *args, **kwargs):
try:
return a_shared_task(self, *args, **kwargs)
except RevokeChainRequested, e:
# Drop subsequent tasks in chain (if not EAGER mode)
if self.request.callbacks:
self.request.callbacks[:] = []
return e.return_value
return inner
此装饰器可用于 shared task
,如下所示:
@shared_task(bind=True)
@revoke_chain_authority
def apply_fetching_decision(self, latitude, longitude):
#...
if condition:
raise RevokeChainRequested(False)
请注意使用@wraps
。有必要保留原始函数的签名,否则后者将丢失,celery
将在调用正确的包装任务时弄乱(例如,它将始终调用第一个注册函数而不是正确的函数)
答案 1 :(得分:8)
从Celery 4.0开始,我发现的工作是使用以下语句从当前task instance's request中删除剩余的任务:
self.request.chain = None
假设您有一系列任务a.s() | b.s() | c.s()
。如果bind the task通过将self
作为参数传递给任务的装饰器,则只能访问任务内的bind=True
变量。
@app.task(name='main.a', bind=True):
def a(self):
if something_happened:
self.request.chain = None
如果something_happened
真实,b
和c
将无法执行。