Delphi TService在Win10及Server 2012R2上的周期性异常排查求助
我之前也维护过类似的Delphi TService多线程项目,这种找不到根源的未处理异常确实头疼,结合你的场景,给你几个针对性的排查方向:
先给服务加上全局异常捕获
默认的TService可能不会把线程内的异常主动抛到调试器,你可以在项目的DPR文件或者TService.OnCreate事件里添加全局异常处理逻辑,把所有异常(包括线程致命异常)写入日志,重点记录调用栈、线程ID、时间戳:procedure GlobalAppException(Sender: TObject; E: Exception); begin TFile.AppendAllText('ServiceCrash.log', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', Now) + ' | Main Thread Exception: ' + E.Message + #13#10 + 'Call Stack: ' + E.StackTrace + #13#10#13#10); end; procedure ThreadTerminateHandler(Sender: TObject); var CurrThread: TThread; FatalEx: Exception; begin CurrThread := Sender as TThread; if Assigned(CurrThread.FatalException) then begin FatalEx := Exception(CurrThread.FatalException); TFile.AppendAllText('ServiceCrash.log', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', Now) + ' | Thread [' + CurrThread.Name + '] Fatal Exception: ' + FatalEx.Message + #13#10 + 'Call Stack: ' + FatalEx.StackTrace + #13#10#13#10); end; end; // 在DPR文件中注册 begin Application.Initialize; Application.OnException := GlobalAppException; TThread.OnTerminate := ThreadTerminateHandler; Application.CreateForm(TYourService, YourService); Application.Run; end.日志里的调用栈信息是定位问题的核心,能直接帮你找到异常触发的代码行。
重点排查轮询线程的资源冲突
2-300ms的高频轮询很容易踩线程安全的坑:- 检查设备通信句柄/连接是否被多个线程共享,有没有用
TCriticalSection或者TMutex做资源保护? - 数据库访问部分,每个线程必须用独立的连接实例(比如每个线程自己创建
TADOConnection),绝对不能共享同一个连接对象;如果用连接池,要确认池的实现是线程安全的。 - 轮询逻辑里的全局变量(比如统计轮询次数、设备状态的变量),必须用
InterlockedIncrement/InterlockedExchange这类原子操作修改,避免多线程读写冲突。
- 检查设备通信句柄/连接是否被多个线程共享,有没有用
换一种调试方式:Attach到运行中的服务进程
直接用调试器启动TService经常会跳过一些异常触发点,你可以先手动启动服务,然后在Delphi里选择Run > Attach to Process,找到你的服务进程(编译后是exe文件),Attach成功后再等待异常发生,调试器大概率能停在异常触发的代码位置。
记得提前开启调试选项里的「Stop on All Exceptions」,不要只勾选Delphi自带的Exception类型。隔离定时任务与轮询逻辑
每日执行的特定操作可能和轮询线程存在资源竞争,比如定时任务需要独占设备或数据库锁,而轮询线程还在持续访问。可以临时注释掉定时任务的代码,观察异常是否还会出现,以此缩小排查范围。检查设备通信的异常边界
轮询设备时,务必处理所有异常场景:设备突然离线、返回数据包不完整、通信超时等。很多时候外部设备的异常如果没被捕获,会直接导致线程抛出未处理异常。比如在串口/网络通信的代码块外层加上try...except,哪怕只是记录日志也比让线程崩溃好。查看Windows系统事件日志
如果服务直接崩溃退出,Windows事件查看器的「应用程序日志」里会记录崩溃的详细信息,包括异常代码、出错的模块名称,这些信息能帮你快速定位是不是第三方控件DLL或者系统API调用出了问题。
内容的提问来源于stack exchange,提问作者tsmr

