import asyncioasync def delay(seconds):await asyncio.sleep(seconds)return f"我睡了 {seconds} 秒"async def main():tasks = [asyncio.create_task(delay(seconds))for seconds in (1, 5, 6)]for finished in asyncio.as_completed(tasks, timeout=3):try:print(await finished)except asyncio.TimeoutError:print("超时啦")# tasks[1] 还需要 2 秒运行完毕,tasks[2] 还需要 3 秒运行完毕print(tasks[1].done(), tasks[2].done())await asyncio.sleep(2)# 此时只剩下 tasks[2] , 还需要 1 秒运行完毕print(tasks[1].done(), tasks[2].done())await asyncio.sleep(1)# tasks[2] 也运行完毕print(tasks[1].done(), tasks[2].done())loop = asyncio.get_event_loop()loop.run_until_complete(main())"""我睡了 1 秒超时啦超时啦False FalseTrue FalseTrue True"""
根据输出结果可以发现,虽然因为抵达超时时间,await 会导致 TimeoutError,但未完成的任务不会受到影响,它们仍然在后台执行 。
但这对于我们来说,有时却不是一件好事 , 因为我们希望如果抵达超时时间 , 那么未完成的任务就别在执行了,这时候如何快速找到那些未完成的任务呢?为处理这种情况,asyncio 提供了另一个 API 函数:wait 。
使用 wait 进行细粒度控制gather 和 as_completed 的缺点之一是,当我们看到异常时,没有简单的方法可以取消已经在运行的任务 。这在很多情况下可能没问题,但是想象一个场景:同时发送大批量 Web 请求(参数格式是相同的),如果某个请求的参数格式错误(说明所有请求的参数格式都错了),那么剩余的请求还有必要执行吗?显然是没有必要的,而且还会消耗更多资源 。另外 as_completed 的另一个缺点是,由于迭代顺序是不确定的,因此很难准确跟踪已完成的任务 。
于是 asyncio 提供了 wait 函数,注意它和 wait_for 的区别,wait_for 针对的是单个任务 , 而 wait 则针对一组任务(不限数量) 。
注:wait 函数接收的是一组 awaitable 对象,但未来的版本改为仅接收任务对象 。因此对于 gather、as_completed、wait 等函数,虽然它们会自动包装成任务,但我们更建议先手动包装成任务 , 然后再传过去 。
并且 wait 和 as_completed 接收的都是任务列表,而 gather 则要求将列表打散,以多个位置参数的方式传递,因此这些 API 的参数格式不要搞混了 。
然后是 wait 函数的返回值 , 它会返回两个集合:一个由已完成的任务(执行结束或出现异常)组成的集合 , 另一个由未完成的任务组成的集合 。而 wait 函数的参数,它除了可以接收一个任务列表之外,还可以接收一个 timeout(超时时间)和一个 return_when(用于控制返回条件) 。光说很容易乱,我们来实际演示一下 。
等待所有任务完成如果未指定 retun_when,则此选项使用默认值,并且它的行为与 asyncio.gather 最接近,但也存在一些差异 。
import asyncioasync def delay(seconds):await asyncio.sleep(seconds)return f"我睡了 {seconds} 秒"async def main():tasks = [asyncio.create_task(delay(seconds)) for seconds in (3, 2, 4)]# 和 gather 一样,默认会等待所有任务都完成done, pending = await asyncio.wait(tasks)print(f"已完成的任务数: {len(done)}")print(f"未完成的任务数: {len(pending)}")for done_task in done:print(await done_task)loop = asyncio.get_event_loop()loop.run_until_complete(main())"""已完成的任务数: 3未完成的任务数: 0我睡了 2 秒我睡了 4 秒我睡了 3 秒"""
await asynio.wait 时 , 会返回两个集合 , 分别保存已完成的任务和仍然运行的任务 。并且由于返回的是集合,所以是无序的 。默认情况下,asyncio.wait 会等到所有任务都完成后才返回,所以待处理集合的长度为 0 。
然后还是要说一下异常,如果某个任务执行时出现异常了该怎么办呢?
import asyncioasync def delay(seconds):await asyncio.sleep(seconds)if seconds == 3:raise ValueError("我出错了(second is 3)")return f"我睡了 {seconds} 秒"async def main():tasks = [asyncio.create_task(delay(seconds)) for seconds in range(1, 6)]done, pending = await asyncio.wait(tasks)print(f"已完成的任务数: {len(done)}")print(f"未完成的任务数: {len(pending)}")loop = asyncio.get_event_loop()loop.run_until_complete(main())"""已完成的任务数: 5未完成的任务数: 0Task exception was never retrievedfuture: <Task finished ... coro=<delay() done, defined at .../main.py:3>exception=ValueError('我出错了(second is 3)')>......raise ValueError("我出错了(second is 3)")ValueError: 我出错了(second is 3)"""
推荐阅读
- 50岁的人应该如何锻炼身体?
- 如何快速通过QQ发布营销消息
- 如何利用琐碎时间减肥?5个提升热量缺口,促进分解
- 跳槽攻略:如何升级身价,全面考虑
- 卧室发现一只蟑螂如何找出窝 蟑螂进屋的十大预兆
- 怎么成为汉服模特,如何成为汉服模特
- 联想一体机如何关闭屏幕,联想小新pro16如何关闭屏幕
- 怎么选择洗面奶是皂基的还是氨基酸 如何区别洗面奶是氨基酸类或是皂类
- 通话语音记录能查到,通话语音内容如何查到
- 怎么挑选耳钉 如何挑选耳钉