对于非常耗时的请求,Tornado提供了异步的方法应对( 比如 AsyncHTTPClient 和 time_out ), 但对于大多是的场景,比如连接数据库,这两个方法并不适用。
翻看源代码的时候发现Tornado提供了一Resolver netutil , 其中有个用线程池实现的非阻塞的Resolver , 用线程池可以解决大部分耗时请求的问题, 参考这段代码可以实现一个通用的解决方案,并且做到与tornado结合。
这里用到了 concurrent.futures 这个包, 对于Python 3.2之前的版本可以直接import concurrent.futures, 3.2之前的需要安装futures 这个包( pip install futures)
代码很短, 大致如下,
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 用于包装函数并且执行
用法如下
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,大功告成