Используя простой генератор Python в качестве подпрограммы в асинхронном обработчике Tornado? - PullRequest
16 голосов
/ 11 января 2012

У меня есть функция генератора Python, которая выдает куски текста.Я хотел бы написать метод get для подкласса tornado.web.RequestHandler, который будет перебирать генератор, записывая фрагменты в ответ по мере его поступления.

Поскольку это 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 ]

16 голосов
/ 11 января 2012

Вот базовая версия того, что вы описываете. Чтобы избежать блокировки, вы можете передать свой генератор в IOLoop через функцию обратного вызова. Хитрость здесь в том, что, поскольку вы не используете процесс, который выполняет фактический ввод-вывод и поэтому не имеет обработчика процесса / файла уровня ОС, который можно добавить в IOLoop через add_handler, вы можете вместо этого использовать простой вызов 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()
14 голосов
/ 11 января 2012

Также возможно использовать новый интерфейс торнадо 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()
...