(十)超时、取消与资源收尾
1. 取消请求到等待处才被处理
# 项目文件:demos/task_cancel.py
# cancel 发出取消请求;继续 await,才能确认任务已经执行收尾并退出。
import asyncio
async def waiting(ready):
try:
print("任务开始")
ready.set()
await asyncio.Event().wait()
finally:
print("清理完成")
async def main():
ready = asyncio.Event()
task = asyncio.create_task(waiting(ready))
await ready.wait()
print("请求取消", task.cancel())
try:
await task
except asyncio.CancelledError:
print("调用者收到取消")
if __name__ == "__main__":
asyncio.run(main())输出按“任务开始、请求取消 True、清理完成、调用者收到取消”推进。cancel() 不是立即把函数从内存中删除,而是请求在可响应的位置注入 CancelledError。
需要记录取消时,可以在 except asyncio.CancelledError 中记录后 raise,把取消继续传出;文件、连接与信号量的释放放到 finally 或相应上下文中。CancelledError 继承 BaseException,普通 except Exception 不会把它当作一般业务失败接住。
2. 在范围之外接收超时
# 项目文件:demos/async_timeout.py
# timeout(0) 让下一次异步等待立即面对到期;finally 仍先执行,外面再收到 TimeoutError。
import asyncio
async def operation(trace):
trace.append("开始等待")
try:
await asyncio.Event().wait()
finally:
trace.append("完成收尾")
async def main():
trace = []
try:
async with asyncio.timeout(0):
await operation(trace)
except TimeoutError:
trace.append("调用者收到超时")
print(trace)
if __name__ == "__main__":
asyncio.run(main())输出 ['开始等待', '完成收尾', '调用者收到超时']。timeout 在范围内通过取消中断当前任务,在退出它的范围时转换成 TimeoutError,所以这里在外面接收超时。
asyncio.wait_for(操作, 秒数) 也能等待并取消超时的操作,它会等待取消收尾,实际返回时间还包括这段退出动作。正在执行、没有让出机会的 Python 计算不会被事件循环的定时器强行切开;线程桥接中的底层线程也有自己的结束过程。