为什么从django pre_save信号发出的芹菜任务永远不会看到该对象

时间:2012-08-29 09:59:12

标签: mysql django celery

这是来自here的后续任务。

Django 1.3.1,芹菜2.2.7,python2.6。

我在fruits/models.py中有以下内容:

考虑模型:

class Fruit(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

以下fruits/tasks.py

from django.dispatch import receiver
from django.db.models import signals

from celery.task import periodic_task, task
import fruits.models as m
import time

@task()
def check_fruit(id):
    time.sleep(2)
    try:
        fruit = m.Fruit.objects.get(pk=id)
        print "Fruit %s is found!" % fruit.name
    except m.Fruit.DoesNotExist:
        print "no such fruit"

@receiver(signals.pre_save, sender=m.Fruit, dispatch_uid="on_fruit_save")
def on_custom_feed_save(sender, instance, **kwargs):
    check_fruit.apply_async(args=[instance.id])

我启动celery守护进程,然后打开django shell并输入:

import fruits.tasks;
import fruits.models as m;
m.Fruit(name="plum").save()

问题我希望这项任务可以找到结果,但它永远不会。为什么呢?

(我是故意从预保存信号启动任务来模拟大型系统上发生的问题)。

3 个答案:

答案 0 :(得分:1)

旧问题其实非常简单,问题不是竞争条件或错误等。

该示例从头开始创建新对象,并且在pre_save信号期间对象尚未存储在数据库中。所以'instance.id'没有设置,对于新创建的对象它是None。

check_fruit.delay(None)

由pre_save信号调用并生成

fruit = m.Fruit.objects.get(pk=None)

DB中没有任何具有空主键的行。

Celery任务实际上是为新创建的对象检查此查询。查询始终按预期抛出异常。

使用 post_save 信号并检查信号的已创建参数是否有新对象。

使用post_save后,可能存在与交易相关的竞争条件。大部分时间你都不在乎,任务重试大多数工作。但是如果你关心竞争条件,请查看新的django功能,on_commit hook

答案 1 :(得分:-1)

本周我遇到了一个非常类似的问题,除了我在没有Django的情况下使用Celery。我发现sender参数仅在任务的实例传递给它时才起作用,而不仅仅是对发送者类本身的引用。

我稍微调查了一下这个问题,发现celery使用sender参数来比较特定任务的id和信号的注册发件人(设置为特定发件人的过滤器。)

在celery / utils / dispatch / signals.py模块中,以下内容执行此评估:

def _make_id(target):  # pragma: no cover
    if hasattr(target, 'im_func'):
        return (id(target.im_self), id(target.im_func))
    return id(target) 

首先获取某个目标对象的id。在装饰器中指定发件人时,它将保存在类似于以下内容的元组中:

lookup_key = (_make_id(receiver), _make_id(sender))

稍后,当一个任务触发时,会调用_live_receivers方法,它实质上会执行一次评估,以查看在lookup_key中指定的目标发件人是否与当前发件人的id匹配(触发任务):

def _live_receivers(self, senderkey):
        """Filter sequence of receivers to get resolved, live receivers.

        This checks for weak references and resolves them, then returning only
        live receivers.

        """
        none_senderkey = _make_id(None)
        receivers = []

        for (receiverkey, r_senderkey), receiver in self.receivers:
            if r_senderkey == none_senderkey or r_senderkey == senderkey:
                if isinstance(receiver, WEAKREF_TYPES):
                    # Dereference the weak reference.
                    receiver = receiver()
                    if receiver is not None:
                        receivers.append(receiver)
                else:
                    receivers.append(receiver)
        return receivers

现在我遇到的问题是,即使我指定了我想要接收信号的任务,当它被触发时,发送方密钥永远不会匹配。

我能够正常工作的唯一方法是在我为特定任务本身构建的抽象任务类的__init__方法中注册信号。通过这样做,我能够向Celery传递它已为该任务注册的确切实例(self)。

我不知道这个问题是关于我的逻辑还是理解信号是如何工作的,或者是否是Celery中的错误,但我知道传递任务实例解决了问题,之后一切正常。

有些注意事项:

  1. 我没有使用Django,因此没有使用Django-Celery扩展
  2. 我不相信Celery存在问题(我更有可能在逻辑上犯了错误或在某处误解了某些内容)
  3. 我不知道这个案例是否适用于你,因为我不确定Django-Celery如何改变Celery后端的工作方式。
  4. 尽管如此,我希望这有用。

    祝你好运!

答案 2 :(得分:-1)

现在可能已经解决了旧的问题和问题,但未来的访问者......

听起来像芹菜工作者打开了一个长时间运行的事务,如果它从未提交过,它永远不会看到新对象。尝试在任务中运行数据库查询之前添加此项:

from django.db import transaction
transaction.commit()

请注意,Django 1.6正在对事务管理进行一些更改,但是在写这篇文章时还没有改变。