python - 如何在 Tornado HTTP 服务器上通过 HTTP 让长任务成为 "cancellable"?

标签 python http asynchronous tornado request-cancelling

我实现了某种重任务的HTTP包装器,我选择了Tornado作为前端服务器框架(因为重任务是用Python写的,我只是习惯了Tornado)。

目前,我只是直接从 Tornado 的进程中调用繁重的任务。我使用 jQuery 准备了某种基于 Web 的界面,让它使用表单中设置的参数处理 AJAX 请求。

如您所想,我从网络浏览器中抛出的任务是不可取消的。我可以取消的唯一方法是向 Python 进程发送 9 或 15 信号,这不是用户通常可以做的。

我想通过 HTTP 请求某种“取消”请求来取消当前工作任务。如何做呢?大多数处理繁重任务(例如 YouTube 中的视频编码)的网络服务在做什么?

最佳答案

实际上 Tornado 的 Futures 不支持取消 ( docs )。此外甚至使用 with_timeout ,超时的作业仍在运行,只是没有等待其结果。

唯一的方法,如 How can I cancel a hanging asyncronous task in tornado, with a timeout? 中所述, 是以可以取消的方式实现逻辑(使用一些标志或其他)。

例子:

  • 工作是一个简单的异步 sleep
  • / 列出作业
  • /add/TIME 添加新作业 - TIME 以秒为单位 - 指定休眠时间
  • /cancel/ID 取消作业

代码可能如下所示:

from tornado.ioloop import IOLoop
from tornado import gen, web
from time import time

class Job():

    def __init__(self, run_sec):
        self.run_sec = int(run_sec)
        self.start_time = None
        self.end_time = None
        self._cancelled = False

    @gen.coroutine
    def run(self):
        """ Some job

        The job is simple: sleep for a given number of seconds.
        It could be implemented as:
             yield gen.sleep(self.run_sec)
        but this way makes it not cancellable, so
        it is divided: run 1s sleep, run_sec times 
        """
        self.start_time = time()
        deadline = self.start_time + self.run_sec
        while not self._cancelled:
            yield gen.sleep(1)
            if time() >= deadline:
                break
        self.end_time = time()

    def cancel(self):
    """ Cancels job

    Returns None on success,
    raises Exception on error:
      if job is already cancelled or done
    """
        if self._cancelled:
            raise Exception('Job is already cancelled')
        if self.end_time is not None:
            raise Exception('Job is already done')
        self._cancelled = True

    def get_state(self):
        if self._cancelled:
            if self.end_time is None:
                # job might be running still
                # and will be stopped on the next while check
                return 'CANCELING...'
            else:
                return 'CANCELLED'
        elif self.end_time is None:
            return 'RUNNING...'
        elif self.start_time is None:
            # actually this never will shown
            # as after creation, job is immediately started
            return 'NOT STARTED'
        else:
            return 'DONE'


class MainHandler(web.RequestHandler):

    def get(self, op=None, param=None):
        if op == 'add':
            # add new job
            new_job = Job(run_sec=param)
            self.application.jobs.append(new_job)
            new_job.run()
            self.write('Job added')
        elif op == 'cancel':
            # cancel job - stop running
            self.application.jobs[int(param)].cancel()
            self.write('Job cancelled')
        else:
            # list jobs
            self.write('<pre>') # this is so ugly... ;P
            self.write('ID\tRUNSEC\tSTART_TIME\tSTATE\tEND_TIME\n')
            for idx, job in enumerate(self.application.jobs):
                self.write('%s\t%s\t%s\t%s\t%s\n' % (
                    idx, job.run_sec, job.start_time,
                    job.get_state(), job.end_time
                ))


class MyApplication(web.Application):

    def __init__(self):

        # to store tasks
        self.jobs = []

        super(MyApplication, self).__init__([
            (r"/", MainHandler),
            (r"/(add)/(\d*)", MainHandler),
            (r"/(cancel)/(\d*)", MainHandler),
        ])

if __name__ == "__main__":
    MyApplication().listen(8888)
    IOLoop.current().start()

添加几个工作:

for a in `seq 12 120`; do curl http://127.0.0.1:8888/add/$a; done

然后取消一些...注意 - 它只需要 Tornado。

这个例子非常简单,gen.sleep 是您的繁重任务。当然不是所有的工作都这么简单,以可取消的方式实现。

关于python - 如何在 Tornado HTTP 服务器上通过 HTTP 让长任务成为 "cancellable"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34472556/

相关文章:

python - 列出python中的重复数据串联

python - 从 python 脚本运行 xdotool movewindow

node.js - 浏览器在连接到服务器时会自动请求 index.html 吗?

asynchronous - Redux Actions 必须是普通对象。使用自定义中间件进行异步操作

c# - 使用异步任务不流动的逻辑 CallContext 值

python - 使用 python 3.4 使用正则表达式获取多个关键字

Python 类型.MethodType()

web-services - 所有的网络服务都是自动的restful 网络服务吗?

Angularjs 从 http.get 获取值

javascript - 异步等待问题