【问题标题】:How to wait for task completes with celery chain?如何等待芹菜链完成任务?
【发布时间】:2020-05-19 04:02:47
【问题描述】:

我正在尝试在这里创建一个芹菜链:

chain(getAllProducts.s(shopname, hdrs),
    editOgTags.s(title, description, whichImage, readableShopname, currentThemeId),
    notifyBulkEditFinish.si(email, name, readableShopname, totalProducts),
    updateBulkEditTask.si(taskID))()

在editOgTags中,有3个子任务:

@shared_task(ignore_result=True)
def editOgTags(products, title, description, whichImage, readableShopname, currentThemeId):
    for product in products:
        editOgTitle.delay(product, title, readableShopname)
        editOgDescription.delay(product, description, readableShopname)
        editOgImage.delay(product, int(whichImage), currentThemeId)

在每个editOgXXX函数中,都有一个函数被调用,有速率限制:

@shared_task(rate_limit='1/s')
def updateMetafield(index, loop_var, target_id, type_value):
    resource = type_value + 's'
    # print(f"loop_var key = {loop_var[index]['key']}")
    if type_value == 'product' or type_value == 'collection' or type_value == 'article' or type_value == 'page':
        meta = shopify.Metafield.find(resource=resource, resource_id=target_id, namespace='global', key=loop_var[index]['key'])
        checkAndWaitShopifyAPICallLimit()
    else:
        print("Not available metafield type! Cannot update.")
        return

    if meta:
        # meta[0].destroy()
        meta[0].value = loop_var[index]['value']
        meta[0].save()
    else:
        metafield = shopify.Metafield.create({
            'value_type': 'string',
            'namespace': 'global',
            'value': loop_var[index]['value'],
            'value-type': 'string',
            'key': loop_var[index]['key'],
            'resource': resource,
            'resource_id': target_id,
            })
        metafield.save()

在漏桶算法下,一次提供40个api调用,2个reqs/s补货。由于 shopify 功能 的速率限制为 2 个请求/秒。我将速率限制设置为 1/s。当它用完 api 配额时,我会在 checkAndWaitShopifyAPICallLimit() 中调用 time.sleep(20) 等待补充。

问题是在所有任务完成之前调用电子邮件通知函数(notifyBulkEditFinish)。如何确保在所有任务完成后调用电子邮件功能?

我怀疑睡眠功能使任务落后于队列中的电子邮件功能。

【问题讨论】:

标签: python celery


【解决方案1】:

扩展@bruno 的评论:使用chord 并修改editOgTags 函数来创建一个与通知和弦的组:

from celery import chord

@shared_task(ignore_result=True)
def editOgTags(products, title, description, whichImage, readableShopname, currentThemeId, name, email, totalProducts):
    tasks = []
    for product in products:
        tasks.append(editOgTitle.si(product, title, readableShopname))
        tasks.append(editOgDescription.si(product, description, readableShopname))
        tasks.append(editOgImage.si(product, int(whichImage), currentThemeId))
    # kick off the chord, notifyBulk... will be called after all of these 
    # edit... tasks complete.
    chord(tasks)(notifyBulkEditFinish.si(email, name, readableShopname, totalProducts))

【讨论】:

  • 请注意 Celery 文档中的这一非常重要的说明: > 在和弦中使用的任务不能忽略它们的结果。实际上,这意味着您必须启用 result_backend 才能使用和弦。此外,如果 task_ignore_result 在您的配置中设置为 True,请确保在和弦中使用的各个任务是使用 ignore_result=False 定义的。这适用于 Task 子类和装饰任务。
  • 不要在任务中使用.get() - docs.celeryproject.org/en/latest/userguide/…
【解决方案2】:

您的问题在于“所有任务完成后”的定义。

editOgTags 启动 len(products) * 3 子任务 - 显然每个子任务都会启动另一个异步子堆栈。如果您想等到所有这些任务都执行完毕后再发送电子邮件,您需要一些同步机制。 Celery 的内置解决方案是 chord 对象。 ATM,您的代码等待editOgTags 完成,但此任务唯一要做的就是启动其他子任务 - 然后它返回,无论这些子任务本身是否完成。

和弦就像一个组,但有一个回调。链原语让我们将签名链接在一起,以便一个接一个地调用,本质上形成一个回调链。把chain改为chord有什么区别?

请注意,我并不是说您必须将整个 chain 替换为 chord。提示:链、组和和弦任务,因此您可以通过组合任务、链、组和和弦来创建复杂的工作流程。

如上所述,不同之处在于chord 将等到其标头中的所有任务完成后才执行回调。这允许并行执行 N 个异步任务,但在运行回调之前仍等待所有任务完成。这当然需要在您的代码中进行一些思考和可能的重组(因此如果需要,将考虑子子任务),但这确实回答了您的问题:“我如何确保在之后调用电子邮件函数所有任务都完成了吗?”

【讨论】:

  • 感谢您的回答!我可以理解我在方法中做错了什么。
  • @BennyChan 很高兴它可以提供帮助 - 然后随时为这个答案投票(这就是在这里说“谢谢”的方式)。
猜你喜欢
  • 2020-07-09
  • 2020-10-11
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 2014-10-28
  • 2014-12-04
  • 2019-06-12
  • 1970-01-01
相关资源
最近更新 更多