在 Django 视图中,我们需要运行一个可能需要数小时才能完成的进程。我们不需要知道进程的状态或与之通信,但我们需要在启动进程后立即重定向视图。
我们尝试了使用 subprocess.Popen,在新的 threading.Thread、multiprocessing.Process 中使用它。但是,父进程会一直挂起,直到子进程终止。唯一几乎可以完成的方法是使用 fork。显然这不是一个好方法,因为它会在父进程终止之前留下一个僵尸进程。
这是我们尝试使用 fork 时所做的事情:
if os.fork() == 0:
subprocess.Popen(["/usr/bin/python", script_path, "-v"])
else:
return HttpResponseRedirect(reverse('view_to_redirect'))
那么,有没有一种方法可以从 Django 视图中运行一个完全独立的进程,同时造成的损失最小?或者我们做错了什么?
2. 解决方案
2.1 使用任务队列
一种解决方案是使用任务队列。当视图被调用时,它会在任务中输入一条新记录并愉快地重定向。然后,任务由 cron 定期执行,独立于 Django。
cron 调用相关的(和自定义的)Django 命令来执行任务。
2.2 使用 Celery
另一个解决方案是使用 Celery,这是一个用于 Django 的任务队列。它基于 RabbitMQ,并提供了一个简单的 API 来创建和管理任务。
2.3 使用 django-command-extensions
django-command-extensions 是一个 Django 库,它提供了扩展 Django 管理命令的功能,包括在后台运行命令。
我们可以使用 django-command-extensions 来创建一个作业,然后在视图中启动作业。
2.4 使用 kronos.py
kronos.py 是一个 Python 库,它提供了一个轻量级的框架来创建和管理长时间运行的进程。
我们可以使用 kronos.py 来创建一个进程,然后在视图中启动进程。
代码例子
使用任务队列
from django.contrib import admin
from django.db import models
class Task(models.Model):
name = models.CharField(max_length=255)
status = models.CharField(max_length=255)
def __str__(self):
return self.name
class TaskAdmin(admin.ModelAdmin):
list_display = ('name', 'status')
admin.site.register(Task, TaskAdmin)
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def start_task(request):
task = Task(name="My Task", status="New")
task.save()
return redirect('task_detail', task_id=task.id)
@csrf_exempt
def task_detail(request, task_id):
task = Task.objects.get(id=task_id)
context = {
'task': task,
}
return render(request, 'task_detail.html', context)
使用 Celery
from celery import Celery
app = Celery('tasks', broker='amqp://guest@localhost//')
@app.task
def long_running_task():
# Do something that takes a long time
return "Task completed"
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def start_task(request):
task = long_running_task.delay()
return redirect('task_detail', task_id=task.id)
@csrf_exempt
def task_detail(request, task_id):
task = long_running_task.AsyncResult(task_id)
context = {
'task': task,
}
return render(request, 'task_detail.html', context)
使用 django-command-extensions
from django_command_extensions.management.commands import job
class LongRunningJob(job.AbstractJob):
def run(self):
# Do something that takes a long time
return "Task completed"
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def start_task(request):
job = LongRunningJob()
job.start()
return redirect('task_detail', task_id=job.id)
@csrf_exempt
def task_detail(request, task_id):
job = LongRunningJob.objects.get(id=task_id)
context = {
'task': job,
}
return render(request, 'task_detail.html', context)
使用 kronos.py
import kronos
class LongRunningProcess(kronos.Job):
def run(self):
# Do something that takes a long time
return "Task completed"
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def start_task(request):
process = LongRunningProcess()
process.start()
return redirect('task_detail', task_id=process.id)
@csrf_exempt
def task_detail(request, task_id):
process = LongRunningProcess.objects.get(id=task_id)
context = {
'task': process,
}
return render(request, 'task_detail.html', context)