Используя простой генератор питона как совместную процедуру в обработчике асинхронного Tornado?
У меня есть функция генератора питона, которая дает куски текста. Я хотел бы написать метод 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
Как я могу заставить этот обработчик работать асинхронно?
Ответы
Ответ 1
Вот базовая версия того, что вы описываете. Чтобы избежать блокировки, вы можете передать генератор в IOLoop с помощью функции обратного вызова. Трюк здесь заключается в том, что вы не используете процесс, который делает фактический IO, и поэтому не имеет обработчика процесса/файла уровня os для добавления в 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()
Ответ 2
Также можно использовать новый интерфейс 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()