在Tornado异步处理器中使用简单的Python生成器作为协程?
我有一个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()