02.09-Agent学习记录-异常处理与资源清理

上一篇介绍了并发与并行,也提到多项工作一起推进以后,需要分别处理它们的结果和失败。接下来就看:查询日历出错,或者通知保存到一半失败时,程序应该怎样继续,已经使用的文件、连接和任务又由谁负责收尾?

舒翱移动端 / 全栈 / Agent 应用

正文

这就是异常处理与资源清理。

先看一个简单情况。会议时长应该以分钟数参与计算,程序收到的却是文字“三十”。如果直接按照整数转换规则处理,就无法得到需要的数值。

在 Python 中,这类问题可以通过异常报告。异常是程序执行中用于表示某种错误或特殊情况的机制。它会打断当前的正常处理路径,并寻找能够处理它的相应代码。

如果没有合适的处理,异常会沿着调用关系继续传递,最终可能使当前任务结束。异常信息通常包含错误类型、说明以及调用位置,帮助人定位问题。Python 的异常与处理机制

先看一段教学代码:

def require_minutes(value):
    minutes = int(value)
    if minutes <= 0:
        raise ValueError("会议时长必须大于零")
    return minutes

try:
    duration = require_minutes("三十")
except ValueError:
    print("会议时长需要重新确认。")
else:
    print(f"会议时长为{duration}分钟。")

require_minutes 先尝试把输入转换成整数,再检查是否大于零。raise 表示主动抛出一个异常:即使转换成功,数值不符合这里制定的规则,也要停止这条正常路径。

try 包住准备执行、但可能失败的操作。except ValueError 表示,这里准备处理这一类异常。示例中的“三十”无法按 int 的规则转换,因此会进入相应分支,输出需要重新确认的说明。

else 中的代码,只在 try 中没有抛出异常时执行。这样,未成功取得时长,就不会继续输出一段看起来已经确认的时长结果。

这段函数只是为了展示处理机制。它并没有覆盖所有类型和业务规则。例如,转换某些数值时可能丢失原本信息,传入不支持的类型也可能产生另一类异常。正式数据接口仍然需要明确输入约定。

异常处理也不是把错误藏起来。

如果查询日历失败,程序捕获异常后随便补一条会议记录,后续步骤也许还能生成通知,但资料已经失去依据。看起来流程没有中断,实际错误却被带进了结果。

合理的处理需要先知道问题是什么,以及当前代码能够做什么。能够修复的可以修复,需要补充信息的可以说明,需要由上层决定的则应保留原因并继续传递。

因此,捕获范围应当与处理能力相匹配。打算处理无效时长,就捕获相关错误;如果所有问题都被宽泛捕获,再统一改成“成功”,程序就会丢失真实状态。

不同失败也不一定都通过抛异常表达。

HTTP 服务可能返回一个错误状态和相应正文,工具可能返回结构化的失败结果,程序库也可能把某些情况转换成异常。开发者需要阅读约定,检查实际得到的状态,不能只根据“函数没有抛错”就认定业务成功。

例如,“查询执行成功,但没有找到会议”与“请求根本没有送达”,是不同情况。前者可能需要调整查询条件,后者则需要处理通信问题,不能只因为最后都没有会议记录,就采用同一种恢复方式。

常见的处理还包括重试,但不是所有错误都适合重试。

暂时的连接问题,也许可以在次数和时间限制内再尝试;字段名称写错、权限不足或业务条件不允许,则需要先解决原因。重复发送同一个无效请求,不会自动使它变得有效。

有副作用的操作还要进一步小心。保存通知超时,不能直接证明文件没有产生。重试前可能需要查询实际结果,或者利用避免重复执行的机制。错误恢复必须考虑操作已经做到了哪里。

这就和任务状态联系起来了。程序可以记录“会议已经确认,通知正文已生成,保存结果尚未确认”,而不是因为最后一步失败,就把前面全部成果都当作不存在。

当错误需要交给上层处理时,也应保留足够的信息。例如,失败的是哪一次工具调用、相关记录编号是什么、已经确认了哪些结果。日志应帮助定位问题,同时避免无差别写入敏感内容。

接下来,看资源清理。

程序除了计算数据,还会取得一些需要释放的资源,例如打开的文件、网络连接、数据库连接、锁,以及本次创建的临时工作空间。如果只处理成功路径,错误发生时,这些资源可能一直被占用。

Python 的 finally 子句,可以用于安排退出相关处理范围时需要执行的收尾。它会在正常返回或异常传播等语言执行路径中运行,适合放置必要的资源释放。

但不宜在 finally 中随意返回成功结果,因为这可能覆盖原来的返回值,甚至掩盖本来应该继续传递的异常。清理与报告成功,承担的是不同职责。finally 的执行与返回值边界

对于文件,更常见的写法是使用 with:

notice = "项目例会草稿"
with open("notice.txt", "x", encoding="utf-8") as file:
    file.write(notice)

这里仍然是演示文本,没有核实真实会议。文件成功打开以后,with 管理它的使用范围。离开代码块时,即使中间写入出现异常,也会进行相应的关闭处理。

这类机制通常称为上下文管理。不同资源可以提供各自的进入和退出逻辑;不只是文件,部分连接或锁也能通过相应方式管理。上下文管理与资源收尾

如果资源一开始就没有成功取得,例如文件根本打不开,程序还需要处理这个失败,不能盲目去关闭一个并不存在的连接。资源是否归当前操作所有,也应当清楚。

关闭文件也不等于撤销已经写下的内容。程序可能已写入一部分,然后发生错误;资源被关闭,文件仍然可能保留不完整内容。需要完整交付时,应当结合适当的写入、核对和提交策略设计。

数据库操作同样如此。捕获了异常,并不自动保证先前修改已经回滚;是否回滚,取决于实际事务与处理机制。后面学习事务和故障补偿时,会继续认识这些关系。

finally 和 with 也不是断电保护装置。它们依赖程序还有机会按相应执行路径运行。强制终止进程、某些直接退出方式或机器故障,都可能使预期清理无法执行。不运行清理处理的退出方式

因此,重要任务不能只寄希望于程序最后有机会收尾。还可以根据需要保存进度,让下次运行能够识别遗留状态,检查哪些结果已经产生,哪些资源需要处理。

异步和并发会增加一些收尾责任。

当前任务被取消时,需要停止继续安排相关工作,并处理自己已经启动的子任务。协程收到取消信号以后,通常应先完成必要清理,再按运行机制要求继续传播取消,不能把它随意改成普通成功结果。异步取消异常的含义

取消本地等待,也不必然终止远程服务或另一个线程已经执行的操作。需要怎样确认停止,取决于相应接口。系统应当知道仍有哪些工作在进行,而不是只在界面上显示一句“已停止”。

资源清理还需要区分保留与删除。生成的通知草稿是用户要的成果,不应该因为任务结束就删除;本次创建且已经无用的临时内容,才属于可以考虑清理的对象。

共享资源更不能随意处理。应用复用了一个公共连接池,或者另一项任务还在使用工作空间时,当前任务结束,并不意味着可以把它们全部关闭或移除。所有权、使用范围和生命周期,需要与清理规则一致。

对于会议助手,一个完整的失败处理可以是:说明保存步骤失败,保留已经核实的会议资料和正文,关闭本次占用的文件或连接,并记录下一步怎样确认保存状态。

这比简单地忽略错误,或者遇到问题就从头全部重做,更能支持可靠的后续处理。具体哪些错误自动恢复、哪些需要用户行动,也可以随着任务边界明确下来。

初学时,可以从每一个会打开外部资源的步骤检查起:失败会怎样报告,成功条件是什么,谁负责释放资源,异常退出后留下什么。把这些问题写清楚,程序的控制关系就会更完整。

下一篇进入 02.10:Git 与版本控制,继续看代码本身发生改动以后,怎样记录版本、比较差异,并在需要时理解和恢复过去的工作。


当前学习编号:02.09 异常处理与资源清理。
下一学习编号:02.10 Git 与版本控制。后续还有其他学习编号。

关联分支:异常定位对应 24.01 日志、指标与分布式 Trace、24.03 错误分类与根因定位;恢复对应 24.04 超时与截止时间、24.05 取消传播与任务确实停止、24.06 重试、指数退避与抖动、24.07 幂等键与去重、24.10 状态恢复与故障补偿。

资源与数据一致性对应 21.02 事务与 ACID、24.13 资源所有权、清理与泄漏防治、18.12 检查点与持久化执行。异常类型、抛出与传播、捕获范围、finally、上下文管理、资源所有权与清理失败,是本编号可继续下钻的支系知识。

系列阅读

本篇学习编号:02.09 异常处理与资源清理。

上一篇: 02.08 并发与并行 。

后续还有:02.10 Git 与版本控制,将在本系列后续发布。

延伸阅读

返回全部文章

搜索项目和文章

输入关键词,查找项目或文章。

使用 Tab 选择结果,按 Esc 关闭。