Python 现代开发学习笔记

(十)超时、取消与资源收尾

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 计算不会被事件循环的定时器强行切开;线程桥接中的底层线程也有自己的结束过程。

到期发出取消,等待处响应,资源先收尾,再由调用者收到超时

笔记正文与全部题解 · Ctrl / ⌘ K