• 异步化

    异步化

    在前面的例子中,我们并没有对RequestHandler中的getpost方法进行异步处理,这就意味着,一旦在getpost方法中出现了耗时间的操作,不仅仅是当前请求被阻塞,按照Tornado框架的工作模式,其他的请求也会被阻塞,所以我们需要对耗时间的操作进行异步化处理。

    在Tornado稍早一些的版本中,可以用装饰器实现请求方法的异步化或协程化来解决这个问题。

    • RequestHandler的请求处理函数添加@tornado.web.asynchronous装饰器,如下所示:

      1. class AsyncReqHandler(RequestHandler):
      2. @tornado.web.asynchronous
      3. def get(self):
      4. http = httpclient.AsyncHTTPClient()
      5. http.fetch("http://example.com/", self._on_download)
      6. def _on_download(self, response):
      7. do_something_with_response(response)
      8. self.render("template.html")
    • RequestHandler的请求处理函数添加@tornado.gen.coroutine装饰器,如下所示:

      1. class GenAsyncHandler(RequestHandler):
      2. @tornado.gen.coroutine
      3. def get(self):
      4. http_client = AsyncHTTPClient()
      5. response = yield http_client.fetch("http://example.com")
      6. do_something_with_response(response)
      7. self.render("template.html")
    • 使用@return_future装饰器,如下所示:

      1. @return_future
      2. def future_func(arg1, arg2, callback):
      3. # Do stuff (possibly asynchronous)
      4. callback(result)
      5. async def caller():
      6. await future_func(arg1, arg2)

    在Tornado 5.x版本中,这几个装饰器都被标记为deprcated(过时),我们可以通过Python 3.5中引入的asyncawait(在Python 3.7中已经成为正式的关键字)来达到同样的效果。当然,要实现异步化还得靠其他的支持异步操作的三方库来支持,如果请求处理函数中用到了不支持异步操作的三方库,就需要靠自己写包装类来支持异步化。

    下面的代码演示了在读写数据库时如何实现请求处理的异步化。我们用到的数据库建表语句如下所示:

    1. create database hrs default charset utf8;
    2. use hrs;
    3. /* 创建部门表 */
    4. create table tb_dept
    5. (
    6. dno int not null comment '部门编号',
    7. dname varchar(10) not null comment '部门名称',
    8. dloc varchar(20) not null comment '部门所在地',
    9. primary key (dno)
    10. );
    11. insert into tb_dept values
    12. (10, '会计部', '北京'),
    13. (20, '研发部', '成都'),
    14. (30, '销售部', '重庆'),
    15. (40, '运维部', '深圳');

    我们通过下面的代码实现了查询和新增部门两个操作。

    1. import json
    2. import aiomysql
    3. import tornado
    4. import tornado.web
    5. from tornado.ioloop import IOLoop
    6. from tornado.options import define, parse_command_line, options
    7. define('port', default=8000, type=int)
    8. async def connect_mysql():
    9. return await aiomysql.connect(
    10. host='120.77.222.217',
    11. port=3306,
    12. db='hrs',
    13. user='root',
    14. password='123456',
    15. )
    16. class HomeHandler(tornado.web.RequestHandler):
    17. async def get(self, no):
    18. async with self.settings['mysql'].cursor(aiomysql.DictCursor) as cursor:
    19. await cursor.execute("select * from tb_dept where dno=%s", (no, ))
    20. if cursor.rowcount == 0:
    21. self.finish(json.dumps({
    22. 'code': 20001,
    23. 'mesg': f'没有编号为{no}的部门'
    24. }))
    25. return
    26. row = await cursor.fetchone()
    27. self.finish(json.dumps(row))
    28. async def post(self, *args, **kwargs):
    29. no = self.get_argument('no')
    30. name = self.get_argument('name')
    31. loc = self.get_argument('loc')
    32. conn = self.settings['mysql']
    33. try:
    34. async with conn.cursor() as cursor:
    35. await cursor.execute('insert into tb_dept values (%s, %s, %s)',
    36. (no, name, loc))
    37. await conn.commit()
    38. except aiomysql.MySQLError:
    39. self.finish(json.dumps({
    40. 'code': 20002,
    41. 'mesg': '添加部门失败请确认部门信息'
    42. }))
    43. else:
    44. self.set_status(201)
    45. self.finish()
    46. def make_app(config):
    47. return tornado.web.Application(
    48. handlers=[(r'/api/depts/(.*)', HomeHandler), ],
    49. **config
    50. )
    51. def main():
    52. parse_command_line()
    53. app = make_app({
    54. 'debug': True,
    55. 'mysql': IOLoop.current().run_sync(connect_mysql)
    56. })
    57. app.listen(options.port)
    58. IOLoop.current().start()
    59. if __name__ == '__main__':
    60. main()

    上面的代码中,我们用到了aiomysql这个三方库,它基于pymysql封装,实现了对MySQL操作的异步化。操作Redis可以使用aioredis,访问MongoDB可以使用motor,这些都是支持异步操作的三方库。