我已将长时间运行的任务分段为逻辑子任务,因此我可以在完成时报告每个子任务的结果。但是,我正在尝试报告一个有效永远无法完成的任务的结果(而不是随着时间的推移而产生价值),并且正在努力使用我现有的解决方案。
我正在为我编写的一些Python程序构建一个Web界面。用户可以通过Web表单提交作业,然后回来查看作业的进度。
假设我有两个函数,每个函数都是通过不同的形式访问的:
med_func
:执行约1分钟,结果传递给render()
,这会产生额外的数据。long_func
:返回一个生成器。每个yield
大约需要30分钟,并且应该向用户报告。收益率如此之多,我们可以将此迭代器视为无限(仅在revoked时终止。)使用med_func
,我会按以下方式报告结果:
在表单提交时,我将AsyncResult
保存到Django session:
task_result = med_func.apply_async([form], link=render.s())
request.session["task_result"] = task_result
结果页面的Django视图访问此AsyncResult
。任务完成后,结果将保存到作为上下文传递给Django模板的对象中。
def results(request):
""" Serve (possibly incomplete) results of a session's latest run. """
session = request.session
try: # Load most recent task
task_result = session["task_result"]
except KeyError: # Already cleared, or doesn't exist
if "results" not in session:
session["status"] = "No job submitted"
else: # Extract data from Asynchronous Tasks
session["status"] = task_result.status
if task_result.ready():
session["results"] = task_result.get()
render_task = task_result.children[0]
# Decorate with rendering results
session["render_status"] = render_task.status
if render_task.ready():
session["results"].render_output = render_task.get()
del(request.session["task_result"]) # Don't need any more
return render_to_response('results.html', request.session)
此解决方案仅在函数终止时有效。我无法将long_func
的逻辑子任务链接在一起,因为yield
s的数量未知(long_func
循环的每次迭代都不会产生结果)。
是否有任何合理的方法可以从极长时间运行的Celery任务中访问产生的对象,以便在生成器耗尽之前显示它们?
答案 0 :(得分:23)
为了让Celery了解任务的当前状态,它会在您拥有的任何结果后端设置一些元数据。您可以通过它来存储其他类型的元数据。
def yielder():
for i in range(2**100):
yield i
@task
def report_progress():
for progress in yielder():
# set current progress on the task
report_progress.backend.mark_as_started(
report_progress.request.id,
progress=progress)
def view_function(request):
task_id = request.session['task_id']
task = AsyncResult(task_id)
progress = task.info['progress']
# do something with your current progress
我不会在那里抛出 ton 数据,但它可以很好地跟踪长时间运行任务的进度。
答案 1 :(得分:9)
mark_as_started
的替代方法,您可以使用Task
的{{1}}方法。他们最终做同样的事情,但名称“update_state”更适合你想要做的事情。您可以选择定义一个custom state来表示您的任务正在进行中(我已将自定义状态命名为“PROGRESS”):
update_state
答案 2 :(得分:6)
芹菜部分:
def long_func(*args, **kwargs):
i = 0
while True:
yield i
do_something_here(*args, **kwargs)
i += 1
@task()
def test_yield_task(task_id=None, **kwargs):
the_progress = 0
for the_progress in long_func(**kwargs):
cache.set('celery-task-%s' % task_id, the_progress)
Webclient方面,启动任务:
r = test_yield_task.apply_async()
request.session['task_id'] = r.task_id
测试最后产生的值:
v = cache.get('celery-task-%s' % session.get('task_id'))
if v:
do_someting()
如果您不喜欢使用缓存,或者不可能,您可以使用数据库,文件或芹菜工作者和服务器端都具有访问权限的任何其他位置。使用缓存它是一个最简单的解决方案,但工作人员和服务器必须使用相同的缓存。
答案 3 :(得分:4)
要考虑的几个选项:
1 - 任务组。如果您可以从调用时枚举所有子任务,则可以将该组作为一个整体应用 - 它返回一个TaskSetResult对象,您可以使用该对象来监视整个组的结果,或者组中的各个任务 - 当您需要检查状态时,根据需要进行查询。
2 - 回调。如果您无法枚举所有子任务(或者即使您可以!),您也可以定义Web挂钩/回调,这是任务的最后一步 - 在任务的其余部分完成时调用。该钩子将针对您的应用程序中的URI,该URI提取结果并通过DB或app-internal API使其可用。
这些组合可以解决您的挑战。
答案 4 :(得分:3)
另见Instagram工程师之一的这个伟大的PyCon preso。
http://blogs.vmware.com/vfabric/2013/04/how-instagram-feeds-work-celery-and-rabbitmq.html
在视频标记16:00,他讨论了如何构建长的子任务列表。
答案 5 :(得分:-1)
就个人而言,我希望看到开始时间,持续时间,进度(产生的项目数),停止时间(或ETA),状态以及任何其他有用的信息。如果它看起来与相关的显示类似,可能就像Linux上的ps
一样。毕竟,这是一个过程状态。
您可以包含一些选项来暂停或终止任务,和/或“打开”它并显示有关孩子或结果的详细信息。