Chapter4_并发网络请求
1、对比如下两段类似代码,解释为什么一个能并发,一个不能?
import asyncio from util import async_timed, delay @async_timed() async def main() -> None: delay_times = [3, 3, 3] [await asyncio.create_task(delay(seconds)) for seconds in delay_times] asyncio.run(main()) —————————————————————————————————————————————————————————————————————————————————————— import asyncio from util import async_timed, delay @async_timed() async def main() -> None: delay_times = [3, 3, 3] tasks = [asyncio.create_task(delay(seconds)) for seconds in delay_times] [await task for task in tasks] asyncio.run(main())
这两段代码的不同之处在于它们如何处理异步任务的创建和等待。让我们详细分析一下原因。
第一段代码
import asyncio from util import async_timed, delay @async_timed() async def main() -> None: delay_times = [3, 3, 3] [await asyncio.create_task(delay(seconds)) for seconds in delay_times] asyncio.run(main())
delay_times是一个包含3个值[3, 3, 3]的列表。- 列表推导式
[await asyncio.create_task(delay(seconds)) for seconds in delay_times]创建和等待每一个任务。 asyncio.create_task(delay(seconds))创建一个新的异步任务,但await立即等待这个任务完成。
原因:由于 await 在列表推导式中直接等待每一个任务完成,这导致这些任务是按顺序(串行)执行的。即,第一个任务完成后,第二个任务才开始,依此类推。因此,总的执行时间是所有任务执行时间的总和(大约 9 秒)。
第二段代码
import asyncio from util import async_timed, delay @async_timed() async def main() -> None: delay_times = [3, 3, 3] tasks = [asyncio.create_task(delay(seconds)) for seconds in delay_times] [await task for task in tasks] asyncio.run(main())
delay_times是一个包含3个值[3, 3, 3]的列表。- 列表推导式
tasks = [asyncio.create_task(delay(seconds)) for seconds in delay_times]创建了所有任务,但没有等待它们完成。 tasks是一个包含三个Task对象的列表。- 列表推导式
[await task for task in tasks]依次等待每一个任务完成。
原因:在这段代码中,所有任务在 tasks 列表创建时几乎同时启动,因为 asyncio.create_task 创建任务时不会阻塞。
然后,[await task for task in tasks] 依次等待每个任务的完成,但这些任务是并发执行的。总的执行时间是单个任务的执行时间(大约 3 秒),因为所有任务是同时进行的。
总结
- 第一段代码:任务是串行执行的,因为每次
await都会等待前一个任务完成后才启动下一个任务。 - 第二段代码:任务是并发执行的,因为所有任务在创建时同时启动,然后等待所有任务完成。
视觉化解释
第一段代码(串行):
Time (seconds) → 0 3 6 9 |----|----|----| T1 (T1) T2 (T2) T3 (T3)
第二段代码(并发):
Time (seconds) → 0 3 |----| T1 (T1) T2 (T2) T3 (T3)
在并发情况下,所有任务同时启动,几乎在相同的时间完成。而在串行情况下,任务一个接一个地执行,导致总时间是每个任务时间的累加。
2、asyncio.gather()的return_exceptions参数的概念的理解
asyncio.gather()函数是用于并发运行多个协程的一个高级工具。其中的return_exceptions参数用于控制协程中产生的异常的处理方式。
概念 return_exceptions参数是一个布尔值,用于指定是否需要从asyncio.gather()返回由于协程引发的异常。
作用
当return_exceptions=False(默认值)时,如果任何一个协程引发异常,asyncio.gather()就会抛出该异常,不再等待其他协程完成。【简单理解,有错就快速报,后面的中断不执行】
当return_exceptions=True时,asyncio.gather()会等待所有协程完成,然后返回由结果和异常构成的列表。【简单理解,顾全大局,等全部执行完,在汇总结果】
通俗解释 假设你让朋友们帮你完成几项任务(协程),你可以选择:
return_exceptions=False(默认)时, 如果有人遇到问题(引发异常), 你立即得到通知(抛出异常), 其他人的工作也无法继续。return_exceptions=True时, 即使有人遇到问题, 你也会等所有人都完成了, 然后你会收到一份报告, 详细列出每个人的结果,包括成功或失败(异常)。
举例说明
return_exceptions=False(默认)
import asyncio async def func1(): return 1 async def func2(): raise ValueError("Error occurred") async def func3(): return 3 async def main(): try: results = await asyncio.gather(func1(), func2(), func3()) print(results) except Exception as e: print(f"Exception raised: {e}") asyncio.run(main())
输出:
Exception raised: Error occurred
由于func2()引发了ValueError异常,asyncio.gather()会立即抛出该异常,而不等待func3()的结果。
return_exceptions=True
import asyncio async def func1(): return 1 async def func2(): raise ValueError("Error occurred") async def func3(): return 3 async def main(): results = await asyncio.gather(func1(), func2(), func3(), return_exceptions=True) print(results) asyncio.run(main())
输出:
[1, ValueError('Error occurred'), 3]
由于return_exceptions=True,asyncio.gather()会等待所有协程完成,并将结果和异常一并返回。结果列表的第二个元素是func2()引发的ValueError异常。
通过这两个例子,可以看出return_exceptions参数对于控制协程中异常的处理方式非常有用。根据具体需求,开发者可以选择合适的值来满足自己的需求。

浙公网安备 33010602011771号