如何在Delphi 12应用程序级捕获所有异常并定位报错位置?
全局捕获Delphi异常并定位代码行的方案
针对你使用Delphi 12 + TIdTCPServer/TThreadProcess组件遇到的偶发EMonitorLockException(提示"unlock list object lock not owned")且无法定位代码行的问题,可通过以下方式全局处理异常并获取调用栈信息:
一、全局异常捕获机制
1. 主线程与VCL线程异常
通过Application.OnException事件捕获主线程及VCL相关线程的异常,在程序启动阶段注册该事件:
procedure TMainForm.FormCreate(Sender: TObject); begin Application.OnException := HandleGlobalException; end; procedure TMainForm.HandleGlobalException(Sender: TObject; E: Exception); var CallStack: string; begin // 获取异常调用栈 CallStack := ExceptionStack(E); // 输出异常详情(可替换为日志写入逻辑) ShowMessageFmt('异常信息: %s'#13#10'调用栈: %s', [E.Message, CallStack]); end;
2. 自定义线程异常(含Indy线程、TThreadProcess)
线程内的异常默认不会触发Application.OnException,需在线程执行逻辑外层包裹try...except捕获,或重写线程的Execute方法:
type TMyIndyWorkerThread = class(TIdThreadWithTask) protected procedure Execute; override; end; procedure TMyIndyWorkerThread.Execute; var CallStack: string; begin try inherited; // 你的线程业务逻辑 except on E: Exception do begin CallStack := ExceptionStack(E); // 同步到主线程显示或写入日志 TThread.Synchronize(nil, procedure begin ShowMessageFmt('线程[%d]异常: %s'#13#10'调用栈: %s', [ThreadID, E.Message, CallStack]); end); end; end; end;
对于TThreadProcess,同样在其执行代码外层添加try...except块,捕获后获取调用栈信息。
二、获取精确调用栈(含代码行号)
要获取代码行号,需确保编译时开启调试信息:
- 打开项目选项(Project Options)
- 进入
Building > Delphi Compiler > Linking - 勾选
Include remote debug symbols和Include TD32 debug info(Debug配置默认已开启)
Delphi 12原生提供ExceptionStack函数(位于System.SysUtils单元),可直接获取包含过程名、代码行号的调用栈。如需更定制化的信息,可使用如下辅助函数:
function GetFullExceptionStack(E: Exception): string; var StackInfoPtr: Pointer; begin Result := ExceptionStack(E); StackInfoPtr := E.StackTrace; if Assigned(StackInfoPtr) then Result := StackInfoToString(StackInfoPtr); end;
三、针对EMonitorLockException的专项排查
该异常属于线程同步错误,通常是解锁了未被当前线程持有的锁,建议:
- 检查所有同步对象(
TCriticalSection、Indy的TIdThreadSafe系列组件等)的使用,确保锁的获取与解锁严格配对,异常路径下不要遗漏解锁操作。 - 开启线程安全检查:在项目选项
Building > Delphi Compiler > Compiling中勾选Enable thread safety checks,调试时可提前发现锁操作违规。 - 为
EMonitorLockException添加异常断点:调试时通过Debug > Add Breakpoint > Exception Breakpoint添加该异常,触发时直接中断到抛出点。
四、调试辅助建议
- 用日志记录替代弹窗:弹窗会干扰线程执行顺序,增加复现难度,建议将异常信息写入日志文件,包含时间戳、线程ID、调用栈。
- 复现问题时尽量保持环境稳定:减少调试时的额外操作,避免因线程调度变化导致异常消失。
内容的提问来源于stack exchange,提问作者Robe79
相关产品推荐
相关产品推荐

