关于 Python 协程的一个问题 (asyncio)

2018-09-24 12:29:47 +08:00
 ltoddy
import asyncio


def foo(n):
    print(f'--------- foo({n}) ----------')


async def main(loop):
    loop.call_later(0.1, foo, 1)
    loop.call_soon(foo, 2)
    loop.call_at(loop.time() + 0.2, foo, 3)

    await asyncio.sleep(1)


event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()

有如上这段简单的代码.

在 main 函数中有 await asyncio.sleep(1) ,如果不写一行, 那么 loop.call_later(0.1, foo, 1)loop.call_at(loop.time() + 0.2, foo, 3) 的结果都会看不到(sleep 的时间必须大于 delay 的时间), 这是为什么.

我的疑问在这里, Python asyncio 的事件循环机制是什么, event_loop.run_until_complete(main(event_loop)) , 这里的 run_until_complete 指的 到底是什么 complete ?

或者说有讲解 Python asyncio event loop 的好的资料告诉我也好.

3790 次点击
所在节点    Python
19 条回复
lanpong
2018-09-24 13:30:30 +08:00
http://lotabout.me/2017/understand-python-asyncio/
早几个月看到的,觉得不错
sujin190
2018-09-24 13:44:34 +08:00
main 函数返回,不是所有事件完成,没有 sleep,main 函数返回 ioloop 就结束了啊,call_later 自然不会执行
guyskk0x0
2018-09-24 13:47:40 +08:00
显然 run_until_complete 指的是你传入的 main()结束
so1n
2018-09-24 13:53:33 +08:00
可以看下这个: https://docs.python.org/3/library/asyncio-eventloop.html
run_until_complete 是指一个 future 运行为一个周期,也就是你传入的 main(event_loop),当没加入 await asynico.sleep()时,run_until_complete 会直接运行完 main(event_loop)然后就执行到你的 event_loop.close()直接关闭事件循环。而你代码中的 loop.call_*是把任务转交给 event_loop 运行,此时的 event_loop 已经关闭,就无法运行了。你可以使用 run_forever()保持事件循环不关闭
ltoddy
2018-09-24 14:16:15 +08:00
@so1n 也就是说 我另外的 两个 `loop.call_*` 是另外的两个 future ,对吧.
zhzer
2018-09-24 15:47:36 +08:00
其实道理不难,就是绕人,我就不献丑了
推荐《 fluent python 》虽然只用了一章讲协程,不过够了
ltoddy
2018-09-24 16:13:19 +08:00
@zhzer 流畅的 py 我已经读了两遍了.
so1n
2018-09-24 16:23:10 +08:00
@ltoddy 恩恩,差不多可以这样理解,不过 await asyncio.wait(tasks)就不一样了还是属于 main 的 future。
ltoddy
2018-09-24 17:51:14 +08:00
@so1n 谢谢了.
neoblackcap
2018-09-24 18:00:16 +08:00
为什么难理解,其实是大家看书查资料的方向错了。这里虽然是协程,但更多的是事件驱动编程,除了用上了 await 来解决以前的装饰器,还有一些默认的封装。但本质还是事件驱动编程。具体可以看 linux 网络编程,跟 epoll 相关的内容。现在大多数事件循环在 Linux 下都是封装 epoll,将对应的套接字以及回调函数注册到 epoll 实例上。抓住本质自然就会了解。其实很多现在这些框架的作者是默认你了解这部分内容的。
PythonAnswer
2018-09-25 07:49:20 +08:00
aio 建议从 3.4 走一遍。那时还没有 await。这玩意其实就是 yield 和 yield from。搞懂了就 ok 了。

不过用了一遍之后,可能还是大坑。

为啥不用 gevent 呢?
ltoddy
2018-09-25 10:36:21 +08:00
@neoblackcap 我觉得你说的很虚, linux 的 epoll 我知道,当初在看 node 以及 libuv 的时候学了.

我想反问,如果你会汇编,难道所有语言对你都是小 case 吗. 所以啊,别说的这么轻浮.
ltoddy
2018-09-25 10:37:20 +08:00
@PythonAnswer 你说的这个我在读流畅的 py 的时候就已经很熟悉了. 为什么不用 gevent, 很简单,我现在还是学生.
PythonAnswer
2018-09-25 12:19:36 +08:00
是学生就好好学吧。加油
zhzer
2018-09-25 13:59:06 +08:00
@ltoddy 额,那不如看看 asyncio 源码吧,我觉得应该是没有预激的原因
说不定是个 bug,研究清楚可以提个 issue

或者先用最新的再试试,3.7 改了很多接口,兴许解决了这些逻辑问题
ltoddy
2018-09-25 14:39:56 +08:00
@zhzer 额,我就是用的 Python3.7, 准备这两天看一下资料,然后总结一下.
cosven
2018-09-25 14:55:15 +08:00
认同 so1n 的说法

之前写过一个非常简单(残疾)地 gevent demo,一百行左右,感觉可以帮助楼主理解 asyncio/gevent 等
https://gist.github.com/cosven/a251ca10c6c0c57c8b5dbd92fe131c2f

在 LZ 的例子中:main 是个协程,另外 call_later/call_soon/call_at 也会创建协程。后来,run_until_complete 只等待 main 结束,就关闭了 event_loop,当 event_loop 关闭了,其它协程自然就不会执行了。

如果 LZ 想让这几个协程都能执行完,可以用 loop.run_forever() 或者一些 asyncio.wait 等其它方法。
ltoddy
2018-09-25 15:32:51 +08:00
@cosven 恩,没错呢.
lolizeppelin
2018-09-26 00:34:22 +08:00
这玩意就是模仿现在 js dart 之类的 异步对象

python 以前也有个别扭的 future 库

他们要实现的功能都差不多 js dart 是原声实现 。
Python 靠 yied 实现

但是万变不离其宗 搞清楚目的就很好理解了

Python3 我没用过 瞎说的 2333

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/492084

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX