【问题标题】:Using a simple python generator as a co-routine in a Tornado async handler?在 Tornado 异步处理程序中使用简单的 python 生成器作为协程?
【发布时间】:2012-02-07 10:07:34
【问题描述】:

我有一个生成文本块的 python 生成器函数。我想为tornado.web.RequestHandler 子类编写一个get 方法,该方法将遍历生成器,将块写入响应中。

因为这是 Tornado,而且生成器可能需要一秒钟的时间来处理,所以我认为最好使处理程序异步,使用这个生成器作为协程并在每个块之后将控制权传递给 IOLoop .但是,我无法确定如何做到这一点。

这是我的示例(阻塞)代码:

class TextHandler(web.RequestHandler):
    @web.asynchronous
    def get(self, n):
        generator = self.generate_text(100000)
        # Clearly, this will block. How to make it asynchronous?
        for text in generator:
            self.write(text)

    def generate_text(n):
        for x in xrange(n):
            if not x % 15:
                yield "FizzBuzz\n"
            elif not x % 5:
                yield "Buzz\n"
            elif not x % 3:
                yield "Fizz\n"
            else:
                yield "%s\n" % x

如何使这个处理程序异步工作?

【问题讨论】:

  • 目前还不清楚您要实现什么目标。您是否想在迭代所有生成器值之前离开 get(),而不是在新值准备好时返回?如果是这样,那么您就不能这样做。在这个特定的函数中,您的代码是单线程的,如果您退出,那么您将失去上下文。另一方面,标记为异步的方法通常意味着处理程序是从线程池中调用的,所以应该可以在那里阻塞。
  • 只要生成器存在,它就有我需要的所有上下文。这就是生成器的美妙之处:单个线程中的协同例程。当然,您必须自己处理调度,这可能是这里真正的问题。

标签: python asynchronous web generator tornado


【解决方案1】:

这是您所描述内容的基本版本。为避免阻塞,您可以通过回调函数将生成器传递给 IOLoop。这里的诀窍是因为您没有使用执行实际 IO 的进程,因此没有操作系统级别的进程/文件处理程序可通过 add_handler 添加到 IOLoop,您可以改为使用简单的 add_callback 调用并从在回调函数中将函数保留在 IOLoop 回调队列中,直到生成器完成。

import tornado.httpserver
import tornado.ioloop
import tornado.web

class TextHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.generator = self.generate_text(1000)
        tornado.ioloop.IOLoop.instance().add_callback(self.loop)

    def loop(self):
        try:
            text = self.generator.next()
            self.write(text)
            tornado.ioloop.IOLoop.instance().add_callback(self.loop)
        except StopIteration:
            self.finish()

    def generate_text(self, n):
        for x in xrange(n):
            if not x % 15:
                yield "FizzBuzz\n"
            elif not x % 5:
                yield "Buzz\n"
            elif not x % 3:
                yield "Fizz\n"
            else:
                yield "%s\n" % x

application = tornado.web.Application([
    (r"/text/", TextHandler),
])

http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()

【讨论】:

  • 为什么是的,这看起来正是我想要的。我没有想过将循环调度本身作为回调。
  • @philofinfinitejest 备注,最好使用 IOLoop.current() 而不是 IOLoop.instance()。就我而言,这很重要。 docs也推荐
【解决方案2】:

也可以使用新的tornado's gen 接口来异步进程:

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.gen

class TextHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):

        def cb(it, callback):
            try:
                value = it.next()
            except StopIteration:
                value = None
            callback(value)

        it = self.generate_text(1000)
        while True:
            response = yield tornado.gen.Task(cb, it)
            if response:
                self.write(response)
            else:
                break
        self.finish()

    def generate_text(self, n):
        for x in xrange(n):
            if not x % 15:
                yield "FizzBuzz\n"
            elif not x % 5:
                yield "Buzz\n"
            elif not x % 3:
                yield "Fizz\n"
            else:
                yield "%s\n" % x

application = tornado.web.Application([
    (r"/text/", TextHandler),
])

http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()

【讨论】:

  • 我想我知道那里发生了什么,但控制流更加神秘(没有深入了解 gen.Task 在幕后所做的事情)。 @cptphil 对预定回调的使用更加直接。
  • 另外,最好使用if response is not None 而不是if response,以防我们使用生成空字符串的生成器。该示例不会,但我的实际用例会。 :)
  • 关于Task 的任何进一步文档?我正在努力通过阅读源代码来理解更大的图景。
猜你喜欢
  • 1970-01-01
  • 2011-06-23
  • 2016-02-02
  • 1970-01-01
  • 1970-01-01
  • 2018-03-31
  • 2020-07-09
  • 2019-07-09
  • 1970-01-01
相关资源
最近更新 更多