看得教程一头雾水, 只能理解那个自带的 http 请求, 看了很久也没看到有比如数据库查询, 文件处理什么的其他例子, 后来去看看其他教程, 实现倒算是勉强实现了, 但感觉也太复杂了吧? ?
就比如例子中的我假设有个长耗时的任务, 怎么写才会简单点?
import tornado.ioloop
import tornado.web
import time
import _thread
# 模拟耗时任务
def long_work(arg):
time.sleep(5)
yield arg * 1024
def mycoroutine(func):
def wrapper(self):
gen = func(self)
work_gen = next(gen)
def fun():
result = next(work_gen)
try:
gen.send(result)
except StopIteration:
pass
_thread.start_new_thread(fun, ())
return wrapper
class IndexHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@mycoroutine
def get(self):
arg = 10 # 假设请求的参数
result = yield long_work(arg)
self.finish(f'<h1>Index {result}</h1>')
class SyncHandler(tornado.web.RequestHandler):
def get(self):
self.write('<h1>SyncHandler</h1>')
def make_app():
return tornado.web.Application([
(r'/', IndexHandler),
(r'/sync', SyncHandler)
])
if __name__ == '__main__':
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
1
Vegetable 2019-01-15 03:09:32 +08:00 via iPhone
拥抱 asyncio 吧…
asyncio 比 tornado 好理解的多,回头再来看 tornado 也好 |
2
feisan 2019-01-15 03:09:38 +08:00
1、尽量使用支持 tornado 异步 io 的库,比如用 tornado_mysql 访问 MySQL。
2、配合 ThreadPoolExecutor 使用,把同步阻塞任务放到线程池。 3、把同步阻塞的处理放到一个单独的进程,提供访问接口,然后通过非阻塞的方式去调用它。比如把数据库访问做成独立的 web 服务,然后在 tornado 里用 HTTP 去访问。或者把同步阻塞任务做成 celery 的 task,使用 tornado_celery 取调用。 |
3
janxin 2019-01-15 10:10:22 +08:00
time.sleep 是阻塞操作,因此你这里模拟耗时任务时也是阻塞的。最新的 Tornado 也是使用的 asyncio 驱动了,所以要么考虑一下了解 asyncio ?
显示上下文切换(async/await/@coroutine)之类的,只有在声明处标示切换,如果某函数未切换(无 await)则仍旧是阻塞执行。另外虽然 yield 是在 Tornado 中用于切换,但是不代表用了就是异步行为,这里比较容易混淆。在使用 async/await 语法之后与普通 yield 行为区分开会更清晰。 |
4
zhengxiaowai 2019-01-15 11:08:48 +08:00
|
5
haozi3156666 2019-01-15 11:31:27 +08:00
数据库查询用 sqlalchemy 也行的,本身框架不带数据库操作相关模块的
|
6
aoscici2000 OP @zhengxiaowai 文章倒是大部分看得懂, 但我发现可能大多新手也跟我一样, 迷惑点是耗时操作如何写成不堵的, 教程例子大多都是直接就用 gen.sleep, async.sleep , 关键是这两个东西是怎么实现的(源码也是看得稀里糊涂的)
|
7
aoscici2000 OP @janxin 看过些, 感觉更难懂哈哈, 主要是很多实例直接就跳过了如何把堵塞的变成不堵的步骤, 我是卡在了这里...比如何如写一个不堵的函数?
|
8
janxin 2019-01-15 13:43:47 +08:00
@aoscici2000 看原理先,不要先看代码。asyncio 的代码质量没有特别高,而且很多边缘状况处理,先从源码很容易迷失。
使用的第一步难道不是先知道我这个是不是异步的么?在 asyncio 中,标记了 coroutine 的都是异步非阻塞的,没标记的都是会阻塞的。先从正确使用开始,换句话说就是没有 await 的地方都有可能阻塞。 |
9
aoscici2000 OP @janxin 头大就头大在第一步, 到底怎么写才算是非阻塞的, 其实很多教程的实例大致还能理解的, 就是这个 asyncio.sleep(x) 实在想知道它是怎么实现的. 例如下面这段的 work 是怎么样才能实现跟 asyncio.sleep 那样不阻塞得拿到完成的数据
```python async def work(sec): time.sleep(sec) return [i*100 for i in range(sec)] async def req_a(num): res = await work(num) print('in req_a:', res) async def req_b(num): res = await work(num) print('in req_b:', res) if __name__ == '__main__': loop = asyncio.get_event_loop() tasks = [req_a(5), req_b(2)] loop.run_until_complete(asyncio.wait(tasks)) print('All finished.') loop.close() ``` |
10
coroutine 2019-01-16 10:45:14 +08:00
你应该先学习一下
1. 事件驱动、IO 多路复用 的知识(CSAPP 和 UNP 里有讲)。 比如先学习一下 select/poll 系统调用,OSX 下的 kqueue, 或者 Linux 下的 epoll(epoll_create, epoll_ctl, epoll_wait)的系统调用知识。 2. Python 本身的 yield, 和 send 分别实现了函数运行时的挂起和唤醒,丢到双端队列里,配合事件驱动每次去取。 然后再回头来看 asyncio 里是如何使用事件驱动的。比如你提到的 async.sleep. 实际就是下一次 epoll timeout 时的返回。 --- Tornado 早期自己利用 epoll 写了事件驱动的源码, 前期也有替换 asyncio 的事件循环的代码: http://www.tornadoweb.org/en/stable/asyncio.html 后来的版本,**似乎**和 asyncio 做了兼容。 另外,从写代码的角度,你可以把直接使用 async await 语法, 而不使用装饰器: http://www.tornadoweb.org/en/stable/guide/coroutines.html#native-vs-decorated-coroutines 另外,《 Python Cookbook 》里也有使用事件驱动来实现同时 handle 多个请求的例子,都可以参考着学习一下。 |
11
coroutine 2019-01-16 10:46:15 +08:00
asyncio.sleep 不是 async.sleep,打错
|
12
coroutine 2019-01-21 14:42:42 +08:00
你如果确实有同步的库需要在异步环境执行,可以参考 https://stackoverflow.com/questions/22190403/how-could-i-use-requests-in-asyncio
|
13
itwhat 2019-05-27 16:00:35 +08:00
py3 用 async 加 await,py2 就用协程装饰器(@gen.coroutine)加 yield
|