在Django中向长时间运行的方法发送信号

时间:2013-04-12 22:50:24

标签: python django signals celery

我想在Celery中向一个长时间运行的任务发送一个“暂停”信号,我正在试图找出最佳方法。在视图中,我可以从数据库中提取对象的实例并告诉它要保存,但它与Celery中对象的实例不同。对象不会检查它是否暂停。

从长时间运行的类中轮询数据库并且任务感觉很奇怪且不切实际所以我正在向我的实例发送消息。我看着使用pubsub,但我更喜欢使用Django信号,因为它已经是一个Django项目了。我可能会以错误的方式接近这一点。

以下是一个不起作用的示例:

Models.py

class LongRunningClass(models.Model):
    is_paused = models.BooleanField(default=False)
    processed_files = models.IntegerField(default=0)
    total_files = models.IntegerField(default=100)

    def long_task(self):
        remaining_files = self.total_files - self.processed_files
        for i in xrange(remaining_files):
            if not self.is_paused:
                self.processed_files += 1
                time.sleep(1)

        # Task complete, let's save.
        self.save()

Views.py

def pause_task(self, pk):
     lrc = LongRunningClass.objects.get(pk=pk)
     lrc.is_paused = True
     lrc.save()
     return HttpResponse(json.dumps({'is_paused': lrc.is_paused}))


def resume_task(self, pk):
    lrc = LongRunningClass.objects.get(pk=pk)
    lrc.is_paused = False
    lrc.save()

    # Pretend this is a Celery task
    lrc.long_task()

因此,如果我修改models.py以使用信号,我可以添加这些行,但它仍然不能正常工作:

pause_signal = django.dispatch.Signal(providing_args=['is_paused'])

@django.dispatch.receiver(pause_signal)
def pause_callback(sender, **kwargs):
    if 'is_paused' in kwargs:
        sender.is_paused = kwargs['is_paused']
        sender.save()

这不会影响已经运行的实例化类。如何告知在任务中运行的模型实例暂停?

2 个答案:

答案 0 :(得分:2)

Celery任务是一个单独的过程。 Django信号是标准的“观察者”模式,它在一个线程内工作,因此没有办法使用信号来组织线程之间的通信。您需要从数据库加载对象以了解其属性是否已更改。

class LongRunningClass(models.Model):
    is_paused = models.BooleanField(default=False)
    processed_files = models.IntegerField(default=0)
    total_files = models.IntegerField(default=100)

    def get_is_paused(self):
        db_obj = LongRunningClass.objects.get(pk=self.pk)
        return db_obj.is_paused

    def long_task(self):
        remaining_files = self.total_files - self.processed_files
        for i in xrange(remaining_files):
            if not self.get_is_paused:
                self.processed_files += 1
                time.sleep(1)

    # Task complete, let's save.
    self.save() 

设计不太好 - 您最好将long_task移动到其他位置,并使用新加载的LongRunningClass实例进行操作,但它会完成这项工作。你可以在这里添加一些内存缓存 - 如果你不想经常打扰你的数据库。

顺便说一句:我不是百分百肯定,但你可能会遇到另一个设计问题。当您使用这种循环执行很长时间的任务时,这种情况非常罕见。考虑从程序中删除循环(你有队列!)。看看:

@celery.task(run_every=2minutes)  # adding XX files for processing every XX minutes
def scheduled_task(lr_pk):
    lr = LongRunningClass.objects.get(pk=lr_pk)
    if not lr.is paused:
        remaining_files = self.total_files - self.processed_files
        for i in xrange(lr.files_per_iteration):
            process_file.delay(lr.pk,i)

@celery.task(rate=1/m,queue='process_file')  # processing each file
def process_file(lr_pk,i):
    #  do somthing with i
    lr = LongRunningClass.objects.get(pk=lr_pk)
    lr.processed_files += 1
    lr.save() 

您必须设置celerybeat,并为此类任务创建单独的队列,以实现此解决方案。但结果你将对你的程序有很多控制 - 速度,并行执行和代码不会挂起sleep(1)。如果为每个文件创建另一个模型,则可以控制处理哪些文件,哪些文件不处理,处理错误等等。

答案 1 :(得分:0)

查看celery.contrib.abortable - 这是Celery任务的备用基类,它在调用者和任务之间实现信号以处理终止,也可用于实现“暂停”。

当来电者拨打abort()时,会在后端标记状态。任务调用self.is_aborted()以查看是否已设置该特殊状态;然后实现适当的任何操作(终止,暂停,忽略等)。行动在任务的控制之下;这不是自动终止任务。

如果特定任务将ABORT信号解释为暂停请求是明智的,则可以按原样使用。或者您可以扩展类以提供更多信号,而不仅仅是现有的ABORT。