2012-10-24 46 views
11

使用Tornado時,我有一個Get請求需要很長時間,因爲它向另一個Web服務發出許多請求並處理數據,可能需要幾分鐘才能完全完成。我不希望這阻止整個Web服務器響應其它請求,它目前所做的。龍捲風阻止異步請求

據我所知,Tornado是單線程的並且同步執行每個請求,即使它異步處理它們(仍然困惑在那個位上)。有很長一段時間的流程可能會暫停點以允許服務器處理其他請求(可能的解決方案?)。我在Heroku上使用單個工作人員來運行它,所以不知道如何轉化爲產生新的線程或多處理,我沒有使用python的經驗。

下面是我正在做的事情:客戶端使get調用來啓動進程,然後我每隔5秒循環一次get call來檢查狀態並用新信息更新頁面(長輪詢會也工作,但遇到同樣的問題)。問題在於,開始漫長的過程會阻止所有新的獲取請求(或新的長輪詢會話),直到完成。

有沒有簡單的方法來啓動這個長時間的調用,並沒有阻止整個Web服務器的過程?有什麼我可以放在代碼說..「暫停,處理待處理的請求,然後繼續」?

我需要在ProcessHandler上發起獲取請求。然後我需要在ProcessHandler正在運行時繼續查詢StatusHandler。

例子:

class StatusHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def get(self): 
     self.render("status.html") 

class ProcessHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def get(self): 
     self.updateStatus("0") 
     result1 = self.function1() 
     self.updateStatus("1") 
     result2 = self.function2(result1) 
     self.updateStatus("2") 
     result3 = self.function3(result2) 
     self.updateStatus("3") 
     self.finish() 
+0

您是否嘗試過tornado.gen模塊? http://www.tornadoweb.org/documentation/gen.html –

+0

你記得把它註釋爲一個異步調用:在你的GET方法上加上@asynchronous –

+0

andyboot是的,我在我的GET方法上有@asynchronous – JeffG

回答

17

下面是一個使用了異步HTTP客戶端和gen.Task模塊,使事情變得簡單了完整的樣品龍捲風應用。

如果您在文檔中閱讀了關於gen.Task的更多信息,您會發現您可以同時分派多個請求。這是使用Tornado的核心思想,其中一切都沒有阻塞,仍然保持單一進程。

更新:我已經添加了一個線程處理程序來演示如何將工作分派到第二個線程,並在完成時接收callback()

import os 
import threading 
import tornado.options 
import tornado.ioloop 
import tornado.httpserver 
import tornado.httpclient 
import tornado.web 
from tornado import gen 
from tornado.web import asynchronous 

tornado.options.define('port', type=int, default=9000, help='server port number (default: 9000)') 
tornado.options.define('debug', type=bool, default=False, help='run in debug mode with autoreload (default: False)') 

class Worker(threading.Thread): 
    def __init__(self, callback=None, *args, **kwargs): 
     super(Worker, self).__init__(*args, **kwargs) 
     self.callback = callback 

    def run(self): 
     import time 
     time.sleep(10) 
     self.callback('DONE') 

class Application(tornado.web.Application): 
    def __init__(self): 
     handlers = [ 
      (r"/", IndexHandler), 
      (r"/thread", ThreadHandler), 
     ] 
     settings = dict(
      static_path = os.path.join(os.path.dirname(__file__), "static"), 
      template_path = os.path.join(os.path.dirname(__file__), "templates"), 
      debug = tornado.options.options.debug, 
     ) 
     tornado.web.Application.__init__(self, handlers, **settings) 

class IndexHandler(tornado.web.RequestHandler): 
    client = tornado.httpclient.AsyncHTTPClient() 

    @asynchronous 
    @gen.engine 
    def get(self): 
     response = yield gen.Task(self.client.fetch, "http://google.com") 

     self.finish("Google's homepage is %d bytes long" % len(response.body)) 

class ThreadHandler(tornado.web.RequestHandler): 
    @asynchronous 
    def get(self): 
     Worker(self.worker_done).start() 

    def worker_done(self, value): 
     self.finish(value) 

def main(): 
    tornado.options.parse_command_line() 
    http_server = tornado.httpserver.HTTPServer(Application()) 
    http_server.listen(tornado.options.options.port) 
    tornado.ioloop.IOLoop.instance().start() 

if __name__ == "__main__": 
    main() 
+0

我在gen.Task中包裝了我的函數,但它仍然做了同樣的事情。我創建了一個有多個response = get.Tasks()的get。我不需要同時執行它們..事實上,它們需要是串行的,但在獲取請求正在進行時,任何其他get請求都會被阻止。 – JeffG

+0

我已經更新了上面的示例。我試圖用gen.Task()包裝所有的函數,並且一切正常,但是它仍然阻止我對StatusHandler上的查詢做出響應,直到它完成。 – JeffG

+0

在你的例子中self.function1()是一個_pure_ python函數,它不會對外部服務進行其他調用嗎?原來的假設是它打電話給另一個服務,並且你被阻止。 – koblas

5

koblas的解決方案非常棒。這是一個替代使用tornado.gen

import tornado.ioloop 
import tornado.web 
import tornado.gen 
import tornado.concurrent 
import time 
from threading import Thread 
from functools import wraps 

def run_async(func): 
    @wraps(func) 
    def async_func(*args, **kwargs): 
    func_hl = Thread(target = func, args = args, kwargs = kwargs) 
    func_hl.start() 
    return func_hl 

    return async_func 

@run_async 
def sleeper(callback): 
    i = 0 
    while i <= 10: 
    print i 
    time.sleep(1) 
    i += 1 
    callback('DONE') 


class MainHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    @tornado.gen.coroutine 
    def get(self): 
     response = yield tornado.gen.Task(sleeper) 
     self.write(response) 
     self.finish() 

class OtherHandler(tornado.web.RequestHandler): 
    def get(self): 
     self.write('hello world') 
     print 'in other' 
     self.finish()