2012-01-11 11 views
16

私は、テキストの塊を生成するpythonジェネレータ関数を持っています。私はtornado.web.RequestHandlerサブクラスに対して、getメソッドを記述してジェネレータを反復し、チャンクをレスポンスに書き出します。単純なpythonジェネレータをTornado非同期ハンドラのコルーチンとして使用しますか?

ジェネレータが処理に1秒以上かかる場合があるので、このジェネレータをコルーチンとして使用して、すべての後に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

+0

ジェネレータが存在する限り、必要なすべてのコンテキストを持っています。それはジェネレーターの美しさです。一つのスレッドでの共同作業です。もちろん、自分でスケジューリングを処理する必要がありますが、これはおそらくここでは本当の問題です。 –

答えて

16

ここには、あなたが説明しているものの基本的なバージョンがあります。ブロッキングを避けるために、コールバック関数を介してジェネレータをIOLoopに渡すことができます。実際のIOを行うプロセスを使用していないので、add_handler経由でIOLoopに追加するosレベルプロセス/ファイルハンドラがないので、代わりに単純な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

なぜそうです、それはまさに私が欲しいもののように見えます。私はコールバックとしてループスケジュールそのものを持つことを考えていませんでした。 –

+1

@philofinfinitejestマイナーな意見では、IOLoop.instance()の代わりにIOLoop.current()を使用する方がよいでしょう。私の場合、それはcrusialでした。 [docs](http://tornado.readthedocs.org/en/latest/ioloop.html?highlight=ioloop#tornado.ioloop.IOLoop.current)でもお勧めします – prokher

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のスケジュールされたコールバックの使用はずっと簡単です。 –

+0

また、空の文字列を生成するジェネレータを使用している場合には、 'レスポンスは'レスポンスではなく 'レスポンスではなく'レスポンスではない 'を使用する方が良いかもしれません。この例はそうではありませんが、私の実際のユースケースはそうです。 :) –

+1

+1はtornado.genを意識していません – philofinfinitejest

関連する問題