如果实现很难解释,可能不是一个好的实现。
如果实现解释起来很简单,应该是一个不错的实现。
我自己有一个玩具项目,叫“信息流邮件”。它的功能就是抓取我感兴趣的博客和RSS,整合成一封邮件发送给自己。这个工具有用之处就在于它使我不用到处检查社交工具的更新,只在一个地方就能获取所有我感兴趣的信息。
这个工具的实现本身并不困难,只是抓取过程有点慢。我添加的信息源越多,抓取的过程就越慢。
这是async函数的一个完美使用场景。
自从我学习了nodejs之后,我就一直想使用一下async函数。我最喜欢的语言还是Python,但有一点nodejs做得更好,就是同时抓取多个api的数据。
虽然nodejs做得很好,但是我还是希望能使用Python完成这个工作,因为Python的实现往往更稳定可靠一些。于是我开始查看Python生态圈中是否有一些替代品。
经过一阵研究,我成功地构建了一个简单的demo,它能够在返回页面之前并行地抓取多个URL的内容。
这个小demo确实可用,就是实现方式怪怪的。下面是它的一个极简版。
Javascript的运行环境默认是以eventloop的形式运行的,Python下你需要明确地调用event_loop,然后把任务放到event_loop中,直到所有的任务都执行完成。
我对fetch函数的实现感到困惑。在async函数中又写了一个async with语句,这样写很奇怪,为什么要这样写呢?
在不写async with的情况下,我又遇到了另外一个奇怪的问题。先展示下这个奇怪的问题的代码:
看起来不错,它打开了一个事件循环,放了一些async函数进去,并且等待它们结束以便搜集结果。只有一个问题:运行得还是很慢。
我在.update()前后加了日志,发现这些任务其实并不是并行的。它们实际上是一个接一个地串行执行的。
文档中找不到任何解释,大多数StackOverflow的评论也是建议使用async库替代async关键字。代码执行看起来有问题,但是我使用关键字,理论上应该能让代码并行运行才对,不是么?
答案是:需要增加一个sleep调用,休眠时间是0秒。
为什么会这样?看起来原因是需要有一些操作使得async函数“暂停然后继续”,才能使gather方法真正地并行运行任务。
在我看来这不可理喻。难道异步函数不应该无论如何都异步运行么?----但是事实就是这样。
Armin Ronacher去年写道《我不理解asyncio》。他关注的问题比我的问题更底层,他研究的是线程的机制。我只是想解决一些简单的实现问题。
直到今日,我的高层次的实现,还是比较复杂。这个问题的来源可以追溯到文档的叙述。
协程的文档字符串中这样写道:
“协程”和“生成器”是两个不同但是相关的概念:
先生们,文档中的这个解释完全没有说中点子。你不能用猫来解释什么是猫,这不是解释,只是反复复述了几遍罢了。
在文档的同一页,文档展示了这个例子
请注意,这里犯了跟我一样的错误:hello_world这个任务实际根本没有异步运行,因为它的代码中没有sleep或者其他什么操作能让当前任务暂停下来,以便后续任务在当前任务还没结束时就启动。如果你想通过这个例子搞清楚Python的异步处理,那么它只会误导你。
如果让我完善一下这个文档例子,我会这样写:
很赞的是,我上面的例子讲得知识已经足够让我完成我想做的功能了。但是糟糕的是,我不确定这种写法是否就是“真正正确的写法”。当我浏览了太多的文章和博客后,我愈发觉得我知道的很可能都是错的。
诗书塞外
https://whatisjasongoldstein.com/
writing/im-too-stupid-for-asyncio/