152 lines
4.9 KiB
Markdown
152 lines
4.9 KiB
Markdown
|
## 异步化
|
|||
|
|
|||
|
在前面的例子中,我们并没有对`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`,这些都是支持异步操作的三方库。
|