2012-10-24 107 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()