## 异步化 在前面的例子中,我们并没有对`RequestHandler`中的`get`或`post`方法进行异步处理,这就意味着,一旦在`get`或`post`方法中出现了耗时间的操作,不仅仅是当前请求被阻塞,按照Tornado框架的工作模式,其他的请求也会被阻塞,所以我们需要对耗时间的操作进行异步化处理。 在Tornado稍早一些的版本中,可以用装饰器实现请求方法的异步化或协程化来解决这个问题。 - 给`RequestHandler`的请求处理函数添加`@tornado.web.asynchronous`装饰器,如下所示: ```Python class AsyncReqHandler(RequestHandler): @tornado.web.asynchronous def get(self): http = httpclient.AsyncHTTPClient() http.fetch("http://example.com/", self._on_download) def _on_download(self, response): do_something_with_response(response) self.render("template.html") ``` - 给`RequestHandler`的请求处理函数添加`@tornado.gen.coroutine`装饰器,如下所示: ```Python class GenAsyncHandler(RequestHandler): @tornado.gen.coroutine def get(self): http_client = AsyncHTTPClient() response = yield http_client.fetch("http://example.com") do_something_with_response(response) self.render("template.html") ``` - 使用`@return_future`装饰器,如下所示: ```Python @return_future def future_func(arg1, arg2, callback): # Do stuff (possibly asynchronous) callback(result) async def caller(): await future_func(arg1, arg2) ``` 在Tornado 5.x版本中,这几个装饰器都被标记为**deprcated**(过时),我们可以通过Python 3.5中引入的`async`和`await`(在Python 3.7中已经成为正式的关键字)来达到同样的效果。当然,要实现异步化还得靠其他的支持异步操作的三方库来支持,如果请求处理函数中用到了不支持异步操作的三方库,就需要靠自己写包装类来支持异步化。 下面的代码演示了在读写数据库时如何实现请求处理的异步化。我们用到的数据库建表语句如下所示: ```SQL create database hrs default charset utf8; use hrs; /* 创建部门表 */ create table tb_dept ( dno int not null comment '部门编号', dname varchar(10) not null comment '部门名称', dloc varchar(20) not null comment '部门所在地', primary key (dno) ); insert into tb_dept values (10, '会计部', '北京'), (20, '研发部', '成都'), (30, '销售部', '重庆'), (40, '运维部', '深圳'); ``` 我们通过下面的代码实现了查询和新增部门两个操作。 ```Python import json import aiomysql import tornado import tornado.web from tornado.ioloop import IOLoop from tornado.options import define, parse_command_line, options define('port', default=8000, type=int) async def connect_mysql(): return await aiomysql.connect( host='120.77.222.217', port=3306, db='hrs', user='root', password='123456', ) class HomeHandler(tornado.web.RequestHandler): async def get(self, no): async with self.settings['mysql'].cursor(aiomysql.DictCursor) as cursor: await cursor.execute("select * from tb_dept where dno=%s", (no, )) if cursor.rowcount == 0: self.finish(json.dumps({ 'code': 20001, 'mesg': f'没有编号为{no}的部门' })) return row = await cursor.fetchone() self.finish(json.dumps(row)) async def post(self, *args, **kwargs): no = self.get_argument('no') name = self.get_argument('name') loc = self.get_argument('loc') conn = self.settings['mysql'] try: async with conn.cursor() as cursor: await cursor.execute('insert into tb_dept values (%s, %s, %s)', (no, name, loc)) await conn.commit() except aiomysql.MySQLError: self.finish(json.dumps({ 'code': 20002, 'mesg': '添加部门失败请确认部门信息' })) else: self.set_status(201) self.finish() def make_app(config): return tornado.web.Application( handlers=[(r'/api/depts/(.*)', HomeHandler), ], **config ) def main(): parse_command_line() app = make_app({ 'debug': True, 'mysql': IOLoop.current().run_sync(connect_mysql) }) app.listen(options.port) IOLoop.current().start() if __name__ == '__main__': main() ``` 上面的代码中,我们用到了`aiomysql`这个三方库,它基于`pymysql`封装,实现了对MySQL操作的异步化。操作Redis可以使用`aioredis`,访问MongoDB可以使用`motor`,这些都是支持异步操作的三方库。