当我需要获得结果并且只有一个工作者时,从任务中调用Celery任务的正确方法是什么?

时间:2016-05-09 16:49:05

标签: python python-2.7 celery

我有一个芹菜队列,只有一个工人(--concurrency=1)。我有两个任务。一个调用API(call_api_task)的人。另一个(other_task)调用一个函数,该函数又调用API任务并等待结果。

def update_data():
    data_to_api = '{"hello": "world"}'
    api_response = call_api_task.subtask(data_to_api).delay().get()
    return api_response

@celery.task
def call_api_task(data_to_api):
    # Call api with data_to_api
    return '{"success": "true"}'

@celery.task
def other_task():
    response = update_data()
    # Do something with response

我遇到的问题是,当other_task被调用时,它永远不会完成。基于Celery docs我认为将任务称为子任务可以避免这个问题,但似乎我错过了一些东西。

我考虑的可能解决方案

  1. update_data添加一个参数,以便让它知道它是从other_task调用的。然后拨打call_api_task,不要subtask& delay

    • 问题:在非平凡的示例代码中,other_taskupdate_data之间还有其他几个函数,这意味着我必须经常传递该参数。这会导致代码混乱。
  2. update_data中添加代码,通过检查billiard.current_process()属性来检测是否通过Celery任务调用了该代码。

    • 问题:似乎应该有更优雅的方法来实现这一目标。
  3. other_task放在不同的Celery队列中。

    • 这就是我目前正在解决的问题。我认为应该有一种更优雅的方式来实现这一目标。
  4. 问题

    update_data如何在不使用上述可能的解决方案#1或#2的情况下从call_api_task调用时直接调用other_task(而不是任务)?

    响应Calling async_result.get() from within a celery task

    的可能重复

    这个问题&答案是专门解决“调用另一个远程任务的celery任务(它位于另一个芹菜应用程序,另一个服务器......)”。对于我的场景,情况并非如此。如上所述,我可以将other_task放在一个单独的Celery队列中,这个队列会产生与此问题的场景相同的最终结果,但我认为应该有更优雅的方法来实现这一目标。

1 个答案:

答案 0 :(得分:0)

您似乎正在寻找一款干净利落的Celery API。

FWIW,您可以将Task子类化为一个,这样您就可以获得您正在寻找的属性,而无需在示例中更改代码。我删除了subtask

注意 - 这使用billiard方法,但将其隔离到代码库的一部分。如果billiard不是一个选项,您可以使用inspect ...但是那个丑陋的IMO。您可以使用billiard.current_process().daemon代替下面说明的更粗略的字符串匹配。

否则,这里还有一些有趣的环境变量方法:How can I detect whether I'm running in a Celery worker?

# Python 3
def update_data():
    """ OP's method without subtask """
    data_to_api = '{"hello": "world"}'
    api_response = call_api_task.delay(data_to_api).get()
    return api_response


class DummyAsyncResult():
    """
    Provides a get() method that returns the value it's initialized with.
    """
    def __init__(self, value):
        self.value = value

    def get(self):
        return self.value


class DelayOnceTask(Task):
    """
    Running .delay() returns a DummyAsyncResult instance if called
    from within a celery worker. Otherwise behaves like a `Task`
    """
    def delay(self, *args, **kwargs):
        if 'PoolWorker' in billiard.current_process()._name:
            return DummyAsyncResult(self(*args, **kwargs))
        else:
            return super().delay(*args, **kwargs)