在Tornado异步处理器中使用简单的Python生成器作为协程?

16 投票
2 回答
6834 浏览
提问于 2025-04-17 09:58

我有一个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

我该如何让这个处理器异步工作呢?

2 个回答

14

你也可以使用新的 tornado的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()
16

这里有一个你描述的基本版本。为了避免阻塞,你可以通过一个回调函数把你的生成器传递给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()

撰写回答