asyncio 系列四、asyncio 的异常

博客介绍了asyncio相关异常,给出官网连接https://docs.python.org/zh-cn/3.7/library/asyncio-exceptions.html 。列举了多种异常情况,如操作超过截止日期、操作被取消等,还说明了部分异常的子类关系及处理模式。

异常

官网连接:https://docs.python.org/zh-cn/3.7/library/asyncio-exceptions.html

 

exception asyncio.TimeoutError

该操作已超过规定的截止日期。

重要:这个异常与内置 TimeoutError 异常不同。

 

exception asyncio.CancelledError

该操作已被取消。

取消asyncio任务时,可以捕获此异常以执行自定义操作。在几乎所有情况下,都必须重新引发异常。

重要:此异常是 Exception 的子类,因此可能会被一个过于宽泛的 try..except 块意外地阻塞:

 

try:

    await operation

except Exception:

    # The cancellation is broken because the *except* block

    # suppresses the CancelledError exception.

    log.log('an error has occurred')

相反,应使用以下模式:

try:

    await operation

except asyncio.CancelledError:

    raise

except Exception:

    log.log('an error has occurred')

exception asyncio.InvalidStateError

 

Task 或 Future 的内部状态无效。

在为已设置结果值的未来对象设置结果值等情况下,可以引发此问题。

exception asyncio.SendfileNotAvailableError

"sendfile" 系统调用不适用于给定的套接字或文件类型。

子类 RuntimeError 。

 

exception asyncio.IncompleteReadError

请求的读取操作未完全完成。

由 asyncio stream APIs 提出

此异常是 EOFError 的子类。

 

expected

预期字节的总数( int )。

 

partial

到达流结束之前读取的 bytes 字符串。

 

exception asyncio.LimitOverrunError

在查找分隔符时达到缓冲区大小限制。

由 asyncio stream APIs 提出

 

consumed

要消耗的字节总数。

<think>我们正在讨论的是`asyncio.gather(*tasks)`的使用和常见问题。根据用户提供的引用和之前的问题,我将详细解释`asyncio.gather`的使用方法、常见错误以及解决方案。 ## 1. `asyncio.gather` 的基本用法 `asyncio.gather`是一个用于并发运行多个异步任务的函数。它接受一系列可等待对象(如协程、任务或Future),并返回它们的结果列表。 ### 示例代码: ```python import asyncio async def task(n): print(f"Task {n} started") await asyncio.sleep(1) print(f"Task {n} completed") return n async def main(): # 创建10个任务(注意:这里直接传递协程对象,gather内部会自动包装成任务) tasks = [task(i) for i in range(10)] # 使用*解包列表,因为gather接受的是可变参数 results = await asyncio.gather(*tasks) print(f"Results: {results}") asyncio.run(main()) ``` ### 关键点: - `asyncio.gather`的参数是可变参数(*args),所以需要传递多个单独的参数,而不是一个列表。因此,我们使用`*tasks`来解包列表。 - 它会等待所有任务完成,并返回一个按输入顺序排列的结果列表。 - 如果其中某个任务引发异常,默认情况下`gather`会立即取消其他任务并抛出异常。但可以通过设置`return_exceptions=True`来将异常作为结果返回。 ## 2. 常见问题及解决方案 ### 问题1:未正确传递参数 **错误示例**: ```python results = await asyncio.gather(tasks) # 错误:传递了一个列表,而不是多个参数 ``` **错误信息**: ``` TypeError: An asyncio.Future, a coroutine or an awaitable is required ``` **解决方案**:使用`*`解包列表。 ```python results = await asyncio.gather(*tasks) ``` ### 问题2:混淆协程函数和协程对象 **错误示例**: ```python tasks = [task] # task是协程函数,不是协程对象 results = await asyncio.gather(*tasks) # 错误:传递的是函数,而不是调用函数返回的协程对象 ``` **解决方案**:调用协程函数以生成协程对象。 ```python tasks = [task(i) for i in range(10)] # 每个元素都是协程对象 ``` ### 问题3:在创建任务列表时使用`await` **错误示例**: ```python tasks = [await task(i) for i in range(10)] # 这里使用了await,会逐个执行,而不是并发 ``` 这样会导致任务按顺序执行,而不是并发执行。 **解决方案**:在列表推导式中不要使用`await`,直接返回协程对象。 ### 问题4:处理异常 默认情况下,如果其中一个任务抛出异常,`gather`会立即取消其他任务并抛出该异常。这可能导致我们无法获取已完成任务的结果。 **解决方案**:使用`return_exceptions=True`参数。 ```python results = await asyncio.gather(*tasks, return_exceptions=True) ``` 然后,我们可以检查每个结果是正常返回还是异常: ```python for result in results: if isinstance(result, Exception): print(f"Task failed: {result}") else: print(f"Task result: {result}") ``` ### 问题5:任务取消行为 当`gather`正在运行的任务被取消时(例如,由于其中一个任务失败而取消其他任务),它会抛出`asyncio.CancelledError`。但如果我们设置了`return_exceptions=True`,则取消异常也会被捕获并作为结果返回。 ## 3. 高级用法 ### 3.1 限制并发数 `asyncio.gather`本身不提供限制并发数的参数。但我们可以使用信号量(`asyncio.Semaphore`)来控制并发数量。 ```python async def run_with_semaphore(sem, n): async with sem: return await task(n) async def main(): sem = asyncio.Semaphore(3) # 最多同时运行3个任务 tasks = [run_with_semaphore(sem, i) for i in range(10)] results = await asyncio.gather(*tasks) print(results) ``` ### 3.2 分组执行 如果需要执行大量任务,但不想一次性全部启动(避免资源耗尽),可以分组执行: ```python async def main(): all_tasks = [task(i) for i in range(100)] group_size = 10 results = [] for i in range(0, len(all_tasks), group_size): group = all_tasks[i:i+group_size] results.extend(await asyncio.gather(*group)) ``` ## 4. 与`asyncio.wait`的区别 - `asyncio.gather`: - 返回任务的结果列表(按输入顺序) - 可以设置`return_exceptions`来处理异常 - 更高级的抽象,专注于收集结果 - `asyncio.wait`: - 返回两个集合:已完成的任务和未完成的任务 - 可以设置超时和返回条件(例如,`return_when=asyncio.FIRST_COMPLETED`) - 需要手动从每个任务中获取结果(通过`task.result()`) ## 5. 总结 使用`asyncio.gather`时需要注意: 1. 正确传递参数:使用`*`解包任务列表。 2. 传递的是协程对象(由协程函数调用返回),而不是协程函数本身。 3. 在列表推导式中不要使用`await`,否则会顺序执行。 4. 使用`return_exceptions=True`来避免因一个任务失败而取消所有任务。 5. 需要限制并发数时,配合信号量使用。 通过正确使用`asyncio.gather`,可以高效地并发执行多个异步任务,并收集它们的结果。 ## 相关问题 1. 如何在使用`asyncio.gather`时处理任务超时? 2. `asyncio.gather`和`asyncio.wait`在性能上有何差异? 3. 如何动态添加任务到`asyncio.gather`中? 4. 在使用`asyncio.gather`时,如何实现任务进度跟踪? 5. 在大量任务并发时,如何优化内存使用? [^1]: 引用[1] [^2]: 引用[2] [^3]: 引用[3] [^4]: 引用[4]</think># asyncio.gather(*tasks) 使用指南与常见问题解析 `asyncio.gather(*tasks)` 是 Python 异步编程中用于并发执行多个异步任务的核心工具。下面我将详细解释其使用方法、常见问题及解决方案。 ## 基本用法与原理 `asyncio.gather()` 接受多个可等待对象(协程、任务或 Future),并**并发执行它们**,返回所有任务的结果列表: ```python import asyncio async def task(n): print(f"任务 {n} 开始") await asyncio.sleep(1) print(f"任务 {n} 完成") return n async def main(): # 创建任务列表(协程对象) tasks = [task(i) for i in range(1, 4)] # 并发执行所有任务 results = await asyncio.gather(*tasks) print(f"所有任务完成: {results}") asyncio.run(main()) ``` 输出: ``` 任务 1 开始 任务 2 开始 任务 3 开始 任务 1 完成 任务 2 完成 任务 3 完成 所有任务完成: [1, 2, 3] ``` ### 关键特性: 1. **并发执行**:所有任务同时启动,不按顺序等待 2. **结果顺序**:返回结果顺序与输入任务顺序一致 3. **自动包装**:非任务对象会自动包装为任务(如协程对象)[^3] 4. **全速冲刺**:默认尽可能多地并发执行任务[^1] ## 常见问题与解决方案 ### 问题1:参数传递错误 **错误信息**:`TypeError: An asyncio.Future, a coroutine or an awaitable is required` **原因**:传递了非可等待对象或未正确解包列表 **解决方案**: ```python # 错误:直接传递列表 results = await asyncio.gather(tasks) # 正确:使用*解包列表 results = await asyncio.gather(*tasks) ``` ### 问题2:任务未正确创建 **错误现象**:任务顺序执行而非并发 **原因**:在创建任务列表时使用了`await` ```python # 错误:使用await会顺序执行 tasks = [await task(i) for i in range(10)] # 正确:直接创建协程对象列表 tasks = [task(i) for i in range(10)] ``` ### 问题3:异常处理不当 **错误现象**:一个任务失败导致所有任务取消 **解决方案**:使用`return_exceptions=True`参数 ```python results = await asyncio.gather( task1(), task2(), task3(), return_exceptions=True # 将异常作为结果返回 ) for r in results: if isinstance(r, Exception): print(f"任务失败: {r}") else: print(f"任务结果: {r}") ``` ### 问题4:资源耗尽(太多并发) **解决方案**:使用信号量限制并发数 ```python semaphore = asyncio.Semaphore(5) # 最大并发数 async def limited_task(n): async with semaphore: return await task(n) async def main(): tasks = [limited_task(i) for i in range(100)] results = await asyncio.gather(*tasks) ``` ## 与 asyncio.wait() 的区别 | 特性 | asyncio.gather() | asyncio.wait() | |---------------------|--------------------------------|--------------------------------| | **返回结果** | 结果列表(按输入顺序) | 完成和未完成任务集合 | | **异常处理** | 支持`return_exceptions`参数 | 需要手动处理 | | **任务控制** | 较少控制选项 | 支持超时和完成条件 | | **结果获取** | 直接返回结果 | 需要从任务对象中提取结果 | | **使用场景** | 简单收集所有结果 | 需要精细控制任务执行 | ```python # asyncio.wait() 示例 async def main(): tasks = [asyncio.create_task(task(i)) for i in range(5)] done, pending = await asyncio.wait(tasks, timeout=2.0) for task in done: print(f"完成任务: {task.result()}") for task in pending: task.cancel() # 取消未完成任务 ``` ## 最佳实践 1. **显式创建任务**:使用`asyncio.create_task()`明确任务对象 ```python tasks = [asyncio.create_task(task(i)) for i in range(10)] ``` 2. **合理限制并发**:对于大量任务,使用信号量控制资源使用 3. **统一异常处理**:始终使用`return_exceptions=True`避免意外中断 4. **任务分组执行**:对于超大规模任务,分批执行 ```python all_results = [] for i in range(0, 1000, 50): # 每次50个任务 batch = tasks[i:i+50] all_results.extend(await asyncio.gather(*batch)) ``` 5. **结合进度跟踪**:使用回调函数跟踪任务进度 ```python def progress_callback(future): print(f"任务完成: {future.result()}") tasks = [] for i in range(10): task = asyncio.create_task(worker(i)) task.add_done_callback(progress_callback) tasks.append(task) await asyncio.gather(*tasks) ``` ## 性能考虑 `asyncio.gather()` 在以下场景中性能最佳: - 任务数量在数百到数千级别 - 任务主要是I/O密集型操作(如网络请求) - 任务执行时间相对均匀 当任务数量超过数万时,建议: 1. 使用分批次执行 2. 考虑使用专门的并发库(如`aiomultiprocess`) 3. 使用`asyncio.Semaphore`限制并发数 通过合理使用 `asyncio.gather()`,可以充分利用 Python 的异步能力,构建高性能的并发应用[^1][^2][^3][^4]。 ## 相关问题 1. 如何在使用 `asyncio.gather` 时实现任务超时控制? 2. `asyncio.gather` 和线程池执行器(ThreadPoolExecutor)有何区别? 3. 如何处理 `asyncio.gather` 中的任务优先级问题? 4. 如何监控 `asyncio.gather` 执行中的任务进度? 5. 在大量任务场景下,`asyncio.gather` 有哪些替代方案? [^1]: 引用[1] [^2]: 引用[2] [^3]: 引用[3] [^4]: 引用[4]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值