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

在Tornado异步处理程序中使用简单的python生成器作为协同例程?,python,asynchronous,web,generator,tornado,Python,Asynchronous,Web,Generator,Tornado,我有一个python生成器函数,可以生成文本块。我想为迭代生成器的tornado.web.RequestHandler子类编写一个get方法,在响应进行时将块写入响应 因为这是Tornado,而且生成器可能需要一秒钟的时间来处理,所以我认为最好使处理程序异步,将此生成器用作一个协例程,并在每个块之后将控制传递给IOLoop。然而,我不知道该怎么做 下面是我的示例(阻塞)代码: 如何使此处理程序异步工作?以下是您所描述内容的基本版本。为了避免阻塞,可以通过回调函数将生成器传递给IOLoop。这里的

我有一个python生成器函数,可以生成文本块。我想为迭代生成器的
tornado.web.RequestHandler
子类编写一个
get
方法,在响应进行时将块写入响应

因为这是Tornado,而且生成器可能需要一秒钟的时间来处理,所以我认为最好使处理程序异步,将此生成器用作一个协例程,并在每个块之后将控制传递给IOLoop。然而,我不知道该怎么做

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


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

以下是您所描述内容的基本版本。为了避免阻塞,可以通过回调函数将生成器传递给IOLoop。这里的诀窍在于,您没有使用执行实际IO的进程,因此没有操作系统级的进程/文件处理程序可通过
add\u 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()

还可以使用新接口来异步进程:

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()

现在还不清楚你要实现什么目标。是否要在迭代所有生成器值之前离开get(),然后在新值准备就绪时返回?如果是这样,你就不能这样做。在这个特定的函数中,您的代码是单线程的,如果退出,则会丢失上下文。另一方面,标记为asynchronous(通常意味着处理程序)的方法是从线程池中调用的,因此,可以在那里进行阻塞。只要生成器存在,它就具有我需要的所有上下文。这就是生成器的美妙之处:在一个线程中执行联合例程。当然,你必须自己处理日程安排,这可能是这里真正的问题。是的,这看起来正是我想要的。我没有想到将循环计划本身作为回调。@PhilofiniteJest次要评论,最好使用IOLoop.current()而不是IOLoop.instance()。就我而言,这是一个残酷的过程。我想我知道那里发生了什么,但控制流程更为神秘(没有深入了解gen.Task在幕后的工作)@cptphil对定时回调的使用更为直接。此外,如果使用的生成器生成空字符串,则最好使用
if response not None
而不是
if response
。示例不会,但我的实际用例会:)关于
任务
的任何进一步文档?我正在努力从阅读资料中了解全局。
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()