2012-01-11 122 views
16

我有一个python生成器函数,它产生大量的文本。我想为tornado.web.RequestHandler子类写一个get方法,它将遍历生成器,将块写入响应中。使用一个简单的Python生成器作为Tornado异步处理程序中的协同例程?

由于这是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 

我怎样才能让这个处理异步工作?

+0

这不是真的清楚你有什么打算来实现。你想在所有的生成器值迭代之前离开get(),并且在新值准备好时返回?如果是这样,比你不能那样做。在这个特定的函数中,你的代码是单线程的,如果你退出,那么你的上下文就会松动。另一方面,标记为异步的方法通常意味着处理程序被称为形成线程池,因此,应该可以在该处阻止。 – real4x 2012-01-11 05:43:12

+0

只要生成器存在,它就拥有我需要的所有上下文。这就是发电机的美妙之处:单一线程中的协同程序。当然,你必须自己处理调度,这可能是真正的问题。 – 2012-01-11 16:12:27

回答

16

下面是您所描述的基本版本。为了避免阻塞,可以通过回调函数将发生器传递给IOLoop。这里的技巧是因为您没有使用真正的IO进程,因此没有os级进程/文件处理程序通过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() 
+0

为什么是,那看起来正是我想要的。我没有想到将循环调度本身作为回调。 – 2012-01-11 16:13:35

+1

@philofinfinitejest小的备注,最好使用IOLoop.current()而不是IOLoop.instance()。在我的情况下,这是严峻的。这也是[docs]推荐的(http://tornado.readthedocs.org/en/latest/ioloop.html?highlight=ioloop#tornado.ioloop.IOLoop.current) – prokher 2015-05-31 21:29:15

14

也可以使用新的tornado's 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() 
+0

我想我知道那里发生了什么,但是控制流程更加神秘(没有深入理解幕后的gen.Task是如何做的)。 @ cptphil对预定回调的使用要简单得多。 – 2012-01-11 18:24:20

+0

另外,如果我们使用一个产生空字符串的生成器,可能会更好地使用'if response is not None'而不是'if response'。这个例子不会,但我的实际用例会。 :) – 2012-01-11 18:25:59

+1

+1不知道tornado.gen – philofinfinitejest 2012-01-11 19:03:43

相关问题