摄影:产品经理暴雨前的宁静
在昨天的文章里面,我说asyncio.create_task(异步函数())
并没有真正运行。
这犯了一个知识性错误。实际上这样写,异步函数里面的代码运行了。例如:
当我们执行异步函数()
时,它返回的是一个coroutine
对象,此时这个异步函数里面的代码是没有运行的。但是当把 coroutine 对象传入asyncio.create_task()
时,异步函数里面的代码就已经开始运行了。
但是,如果我们直接把昨天代码里面的 asyncio.gather
去掉,也会导致报错,如下图所示:
可以看到,两个内部不含 await
的异步函数成功执行了,但是调用 aiohttp 的两个异步函数却报错了。
这是由于,当我们的代码运行到第32行时,它并不会停下来,而是直接退出了 aiohttp 的缩进,此时,aiohttp 的 Session 就已经关闭了。过了一会,网站请求的返回才回来,可是由于 Session 关闭,于是导致报错。
在这个例子里面,由于我们知道哪个异步任务最耗时,所以我们可以人工等待它:
此时程序就能正常运行了。这里看起来就像调用多线程里面的.join()
一样。当我们调用 await 任务的时候,程序一定会在第33行等待5秒延迟的这个任务。一开始的时候我们有4个异步任务,asyncio 在这四个任务之间调度。后来前3个任务都结束了,asyncio 就直接等着最后一个任务结束。等到5秒延迟返回了,程序才会退出 aiohttp 的缩进。所以程序就不会报错了。
但在实际使用中,我们可能并不知道哪个异步任务耗时最久,所以有两种解决方案:
第一种是模拟多线程的情况,写一个循环,执行一个很长的睡眠时间,或者直接死循环:
但这种方案有个弊端,就是可能真正的任务已经全部运行完了,你循环的时间还没有结束。
第二种方案,跟昨天一样,使用asyncio.gather
,这样可以保证所有任务运行完成以后,立刻返回。并且,asyncio.gather
不仅能接受异步任务,也能直接接收coroutine对象:
当我们不使用asyncio.gather
时,其实代码的运行逻辑就跟 JavaScript 原生的异步相差不大了。
最后,感谢🐳klew指出昨天文章中的问题。