
做工业现场、产线工位开发的朋友应该都懂这种无力感好好的无人值守工位、数据采集站、自动检测设备白天跑着一切正常结果第二天上班一看程序界面卡死在那儿或者进程早就没了一晚上的数据没存、产线停了半宿追责都不知道找谁。很多人对付程序崩溃就是随手写个批处理循环判断进程名不在就启动。说实话这种方案只能对付“程序直接闪退、进程彻底退出”的初级崩溃真遇到内存泄漏假死、UI线程卡死、进程活着但业务停摆的情况完全就是摆设。更坑的是有时候程序崩了又启、启了又崩无限循环最后把系统资源耗光连远程桌面都连不上。这几年做过十几条产线的无人值守改造从最简单的打标工位到整线的视觉检测站踩遍了各种重启方案的坑最终沉淀出一套四层兜底的自动重启体系。从最基础的进程监控到业务级心跳检测再到系统级服务兜底最后加上异常熔断告警不仅能做到崩溃自动重启还能区分崩溃类型、保留现场、避免无限死循环。整体架构四层兜底从进程到系统全覆盖先上一张完整的方案架构图从下到上逐层防护越底层越基础越上层越接近业务最终靠策略层避免恶性循环。整个方案的设计原则很明确底层解决“有没有”的问题上层解决“好不好用”的问题策略层解决“不添乱”的问题。每一层都有自己的适用场景和局限性组合起来才能覆盖绝大多数崩溃场景。第一层进程级监控——基础崩溃的快速兜底这是最容易实现、也是最基础的一层核心目标是只要进程退出了就立刻拉起来。常见实现方式与优化很多人最开始用的都是批处理脚本echo off :loop tasklist | find /i YourApp.exe nul if %errorlevel% neq 0 ( start D:\YourApp.exe echo %date% %time% 程序重启 restart_log.txt ) timeout /t 5 /nobreak nul goto loop这种写法能用但很粗糙。几个关键优化点不要死循环无间隔必须加延时建议5-10秒检测一次不然空耗CPU一定要打日志每次重启都记录时间事后排查才有依据启动前等待一下程序崩溃后可能资源还没释放等2秒再启动避免端口/文件占用报错用绝对路径不要用相对路径避免工作目录不对导致启动失败更稳定的进阶方式任务计划程序其实Windows自带的任务计划程序就有“失败时重启”的能力比自己写批处理更稳定。创建任务触发器设置为“程序启动时”操作设置为启动目标程序在“设置”里勾选“如果任务失败则按以下频率重新启动”设置间隔1分钟尝试3次再额外创建一个定时检测任务每5分钟检查一次进程状态这一层的局限性进程级监控有个致命盲区只能检测进程存不存在管不了进程活不活着。程序UI卡死、消息循环阻塞、业务线程挂死只要进程没退出它就认为一切正常。而无人值守场景里80%的崩溃恰恰都是这种“假死”状态。之前有个视觉检测工位程序因为内存泄漏每天凌晨都会卡死进程还在但界面不动了相机也停拍了。用普通进程监控根本发现不了直到早班人员到现场才知道一晚上漏检了几百件。第二层业务级心跳检测——解决90%的假死问题要判断程序是不是真的在正常运行不能问操作系统要问程序自己。这就是心跳机制的核心逻辑。心跳机制的原理主业务程序定时更新一个“心跳标记”可以是一个文件的修改时间、注册表的某个键值、或者共享内存的一个时间戳。独立的监控程序定时去读这个标记如果超过约定时间没更新就判定程序已经假死强制结束进程并重启。心跳设计的几个关键细节这几个点做错了心跳等于白做心跳绝对不能跑在UI线程很多人把心跳写在主窗口的定时器里UI线程一卡死心跳也就停更了看似没问题但如果是业务线程挂死、UI还能响应的情况就完全检测不出来。正确的做法是把心跳放在独立的后台工作线程里和主业务线程分开。超时阈值要合理一般设置为心跳周期的3倍。比如每10秒更新一次心跳超时就设30秒。太短容易误判太长故障发现不及时。工业场景建议10秒周期30秒超时兼顾灵敏度和稳定性。加上资源占用辅助判定除了心跳还要辅助检测进程的CPU和内存占用CPU持续100%超过30秒大概率是死循环内存持续暴涨超过阈值大概率是内存泄漏满足任一条件即使心跳还在跳也要触发重启把问题扼杀在萌芽状态。心跳实现核心代码C#业务程序侧的心跳更新放在独立线程// 心跳文件路径 private readonly string _heartbeatPath D:\runtime\heartbeat.dat; private readonly int _heartbeatInterval 10000; // 10秒 // 启动心跳线程 private void StartHeartbeat() { var thread new Thread(() { while (true) { try { // 更新文件修改时间作为心跳标记 File.WriteAllText(_heartbeatPath, DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss)); } catch { // 心跳写入失败本身就是异常记录日志 } Thread.Sleep(_heartbeatInterval); } }) { IsBackground true, Priority ThreadPriority.AboveNormal }; thread.Start(); }监控程序侧的检测逻辑private bool CheckAppHealth() { if (!File.Exists(_heartbeatPath)) return false; var lastUpdate File.GetLastWriteTime(_heartbeatPath); if ((DateTime.Now - lastUpdate).TotalSeconds 30) return false; // 辅助检查进程是否存在 var process Process.GetProcessesByName(YourApp).FirstOrDefault(); if (process null) return false; // 辅助检查内存占用 if (process.WorkingSet64 2L * 1024 * 1024 * 1024) // 超过2G return false; return true; }第三层系统级服务兜底——解决监控自己也崩的问题很多人做了监控程序去监控业务程序但忽略了一个问题监控程序自己崩了怎么办用普通的exe程序做监控和业务程序本质上是同级的业务程序能崩监控程序也能崩。尤其是遇到系统资源不足、DLL冲突的时候很容易一起挂掉。最优解用Windows服务做监控宿主Windows服务是系统内核级的进程优先级远高于普通应用程序有几个天然优势开机自动启动不需要用户登录注销用户也不会停止系统自带失败恢复策略可以配置第一次失败重启、第二次失败重启、第三次失败重启计算机不容易被普通的异常影响稳定性远高于普通exe程序把监控逻辑封装在Windows服务里相当于给监控程序又加了一层系统兜底。业务程序崩了服务拉起来服务崩了系统拉起来系统都崩了还能设置自动重启计算机。系统级的终极兜底蓝屏自动重启如果遇到驱动冲突、硬件异常导致系统蓝屏、死机那什么服务都没用了。这时候要靠BIOS和系统的自带机制Windows系统失败自动重启系统属性→启动和故障恢复→勾选“系统失败时自动重新启动”BIOS看门狗很多工控机主板都带硬件看门狗超过时间没喂狗就自动复位这是最底层的兜底第四层异常熔断与现场保留——避免无限重启恶性循环自动重启不是万能的如果程序本身有致命bug、或者硬件故障重启了马上又崩无限循环只会越搞越糟甚至把系统搞瘫。重启熔断机制必须设置重启次数限制和时间窗口比如1小时内重启超过3次就停止自动重启触发熔断后每30分钟尝试重启一次成功则恢复正常监控连续3次尝试都失败就彻底停止触发告警核心逻辑private readonly ListDateTime _restartRecords new(); private readonly int _maxRestartPerHour 3; private bool _isFused false; private bool CanRestart() { if (_isFused) return false; // 移除1小时前的记录 _restartRecords.RemoveAll(t (DateTime.Now - t).TotalHours 1); if (_restartRecords.Count _maxRestartPerHour) { _isFused true; TriggerAlarm(程序频繁崩溃已触发熔断停止自动重启); return false; } _restartRecords.Add(DateTime.Now); return true; }崩溃现场保留每次重启前一定要保留现场不然根本不知道为什么崩的程序崩溃自动转储Dump通过注册表或者程序内置异常处理崩溃时生成完整dump文件重启前记录系统状态记录当时的CPU、内存、磁盘占用端口占用情况业务日志分级输出关键操作都留痕出问题能回溯到崩溃前的最后一步告警通知熔断之后不能就停在那儿必须通知到人本地声光告警接个蜂鸣器或者警示灯现场有人的话能立刻发现网络告警通过企业微信、钉钉、邮件发送通知远程也能收到重要工位可以接短信告警确保第一时间有人处理现场踩坑避坑指南这些都是实打实踩出来的经验每一条都对应过现场故障。1. 绝对不要用UI程序做监控普通exe程序一注销用户就退出了无人值守工位经常设置自动登录但遇到组策略更新、系统补丁重启没登录进去监控就完全失效。一定要用Windows服务不需要登录也能运行。2. 杀进程一定要杀干净很多程序会启动子进程比如相机驱动、串口助手、子模块exe。只杀主进程的话子进程还占着端口和硬件资源重启就会失败。结束进程的时候要遍历整个进程树把子进程一起杀掉。3. 重启前要优雅关闭不行再强杀不要一上来就强制结束进程先发关闭消息给程序留几秒保存数据、释放资源的时间。等5秒还没退出再强制Kill。不然很容易出现配置文件损坏、数据丢失的情况。4. 不要把心跳周期设太短见过有人设1秒心跳、3秒超时系统一卡就误判重启反而导致业务中断。工业场景不需要那么高的灵敏度稳定优先10-30秒的检测周期完全足够。5. 监控程序本身要极简监控程序就做监控一件事不要往里塞业务逻辑、不要引用一堆第三方库。越简单的东西越稳定监控程序自己的稳定性比什么都重要。总结无人值守工位的自动重启从来不是“写个批处理循环判断进程”这么简单。它本质上是一套完整的高可用体系进程层解决最基础的闪退问题业务层解决假死卡死的深层问题系统层解决监控自身的可靠性问题策略层解决无限重启的恶性循环问题四层兜底层层防护再加上现场保留和告警机制才能真正做到7×24小时无人值守不用半夜跑现场救急。做工业控制系统永远是“防大于治”。把这些兜底机制做扎实比出了问题再去救火效率高得多也靠谱得多。