2012-01-11 54 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

相關問題