看得教程一头雾水, 只能理解那个自带的 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 |