面试题答案
一键面试异常传播机制
- 默认行为:在Python的
asyncio
库中,当父协程被取消时,子协程如果没有处理CancelledError
,它们会被取消,并且CancelledError
会在子协程中被抛出。如果子协程在处理CancelledError
的过程中又抛出了其他异常,这些异常默认不会向上传播到父协程。 - 捕获和处理异常:为了让子协程的异常能够传播到父协程,子协程需要捕获
CancelledError
,并在处理时将其他异常重新抛出。
实现思路
- 子协程:
- 捕获
CancelledError
。 - 在处理
CancelledError
时,如果有其他异常,将其重新抛出。 - 使用
try - finally
块来确保资源在子协程结束时被释放。
- 捕获
- 父协程:
- 捕获子协程抛出的异常。
- 使用
try - finally
块来确保父协程取消时资源的释放。
关键代码结构
以下是Python asyncio
库中的示例代码:
import asyncio
async def child_coroutine():
try:
# 模拟子协程的工作
await asyncio.sleep(1)
raise ValueError("子协程出现异常")
except asyncio.CancelledError:
# 处理取消异常
print("子协程被取消")
# 如果还有其他异常,重新抛出
raise
finally:
# 释放资源,如关闭文件、数据库连接等
print("子协程资源释放")
async def parent_coroutine():
tasks = [asyncio.create_task(child_coroutine()) for _ in range(3)]
try:
await asyncio.gather(*tasks)
except ValueError as ve:
# 捕获子协程抛出的异常
print(f"捕获到子协程异常: {ve}")
except asyncio.CancelledError:
# 处理父协程取消异常
print("父协程被取消")
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
finally:
# 释放资源,如关闭文件、数据库连接等
print("父协程资源释放")
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(parent_coroutine())
except KeyboardInterrupt:
print("手动取消")
tasks = asyncio.all_tasks(loop)
for task in tasks:
task.cancel()
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
finally:
loop.close()
child_coroutine
:try
块:包含子协程的业务逻辑,这里模拟了一个睡眠操作并抛出ValueError
。except asyncio.CancelledError
块:处理子协程被取消的情况,并重新抛出其他异常。finally
块:确保无论子协程是正常结束、被取消还是抛出异常,资源都能被释放。
parent_coroutine
:- 创建多个子协程任务并使用
asyncio.gather
运行。 try
块:捕获子协程抛出的ValueError
。except asyncio.CancelledError
块:处理父协程被取消的情况,取消所有子协程任务并等待它们完成。finally
块:确保父协程取消时资源的释放。
- 创建多个子协程任务并使用
通过上述方式,可以确保在父协程取消时,子协程的异常能够被正确捕获,并且资源能够得到合理释放。