对于非常耗时的请求,Tornado提供了异步的方法应对( 比如 AsyncHTTPClient 和 time_out ), 但对于大多是的场景,比如连接数据库,这两个方法并不适用。
翻看源代码的时候发现Tornado提供了一Resolver netutil , 其中有个用线程池实现的非阻塞的Resolver , 用线程池可以解决大部分耗时请求的问题, 参考这段代码可以实现一个通用的解决方案,并且做到与tornado结合。
这里用到了 concurrent.futures 这个包, 对于Python 3.2之前的版本可以直接import concurrent.futures, 3.2之前的需要安装futures 这个包( pip install futures)
代码很短, 大致如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 | from concurrent.futures import ThreadPoolExecutor from tornado.ioloop import IOLoop from tornado.concurrent import run_on_executor class AsyncUtils: def __init__(self,num_worker=10): self.io_loop = IOLoop.current() self.executor = ThreadPoolExecutor(num_worker) @run_on_executor def cmd(self,func, *args, **kwargs): res = func(*args,**kwargs) return res |
其中 @run_on_executor, 是tornado提供的一个将同步的方法转为异步的装饰器, 适用这个装饰器要求要有 self.io_loop 和 self.executor 两个变量在, self.executor 执行完任务直接将结果丢给self.io_loop去处理。
cmd 用于包装函数并且执行
用法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import tornado.ioloop import tornado.web import tornado.gen import time _ASYNC = AsyncUtils(5) #线程池大小为5 class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") class MainAsyncHandler(tornado.web.RequestHandler): @tornado.web.asynchronous @tornado.gen.coroutine def get(self): res = yield _ASYNC.cmd(time.sleep,10) #sleep 10s self.write("Hello, world2") application = tornado.web.Application([ (r"/", MainHandler), (r"/async", MainAsyncHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() |
这时候打开127.0.0.1:8888/, 会立即打印hello world, 打开http://127.0.0.1:8888/async 会进入sleep, 这时候浏览器是等待状态, 此时打开 http://127.0.0.1:8888/ ,依然会打印hello world , 没有出现卡死状态,等10s后刚刚那 http://127.0.0.1:8888/async 也打印出了hello world2,大功告成