阻塞
函数阻塞时会一直等待处理的结果才返回。阻塞情况比如网络I/O,硬盘I/O等。事实上,每一个函数都会阻塞,最少是一点点,因为他们在运行而且要使用Cpu(最有代表性的是密码hash函数bcrypt,要使用cpu的毫秒时间)。
一个函数在一些情况下会阻塞,但是在另外一些情况下不会阻塞。如tornado.httpclient 在DNS解决方案下,当采取默认的配置时会阻塞,但是在其他网络访问的情况下不会阻塞(比如使用ThreadedResolver 或tornado.curl_httpclient)。
异步
一个异步函数在函数结束之前就可以返回,通常在触发一些未来的动作之前会引起后台处理处理一些工作。下面有几种异步接口:
- Callback 参数(回调函数)
- 返回一个占位符(比如Future、Promise、Deferred)
- 发送至一个队列中(Queue)
- 回调注册(POSIX signals)
异步化
- 给RequestHandler的请求处理函数添加@tornado.gen.coroutine装饰器
- 给RequestHandler的请求处理函数添加@tornado.web.asynchronous装饰器
- 使用@return_future装饰器
from tornado import gen
@gen.coroutine
def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = yield http_client.fetch(url)
# 在Python 3.3之前, 在generator中是不允许有返回值的,必须通过抛出异常来代替. 如raise gen.Return(response.body).
return response.body
在Tornado 5.x版本中,这几个装饰器都被标记为deprcated(过时),我们可通过Python3.5中引入的async和await(在Python3.7中已经成为正式关键字)来达到同样的效果。当然,要实现异步化还得靠其他的支持异步操作的三方库来支持,如果请求处理函数中用到了不支持异步操作的三方库,就需要靠自己写包装类来支持异步化。
- Postgresql: asyncpg
- Redis: aioredis
- I/O Framework:uvloop
@gen.coroutine 与 @gen.engine
tornado.gen 支持以同步方式编写异步代码的核心就是 python generator。其原理简单来说,就是通过 generator.next() 启动 yield 返回的 generator ,通过 IOLoop 与 generator.send(value) 驱动 generator 运行,以达到协调异步执行的目的。
从功能上来看, @gen.coroutine 与 @gen.engine 的功能非常相似,差别就在于二者对被装饰方法参数中的 “callback” 参数处理不一样以及具有不同的返回值。
- @gen.coroutine 装饰的方法执行后返回 Future 对象并且会将方法参数中的 “callback” 加入到 Future 完成后的回调列表中;
- @gen.engine 装饰的方法执行后没有返回值(注:实际上如果被装饰方法有返回值,会抛出 ReturnValueIgnoredError 异常,详见后面的代码分析部分)。
所以,通过 @gen.engine 装饰的方法没有返回值,方法必须自己在异步调用完成后调用 “callback” 来执行回调动作,而通过 @gen.coroutine 装饰的方法则可以直接返回执行结果,然后由 gen 模块负责将结果传递给 “callback” 来执行回调。
@tornado.gen.coroutine + yield + raise gen.Return(d)
- 当调用一个协程时,@tornado.gen.coroutine 与 yield 必须同时出现调用函数中
- 如果只是在协程中执行操作或者直接返回结果,有 @tornado.gen.coroutine 和 return(raise Return)就够了