modbus 512 断线重连 db browser for sqlite
断线重连
privateasyncTaskHeartbeatLoopAsync(CancellationTokentoken){// 监工一直循环干活,直到工长喊停工(token.IsCancellationRequested)while(!token.IsCancellationRequested){try{// 每隔一段时间检查一次(最少200ms,默认你设置的心跳间隔)awaitTask.Delay(Math.Max(200,HeartbeatIntervalMs),token).ConfigureAwait(false);}catch(OperationCanceledException){break;// 工长喊停工,监工立刻下班}// 拿一下:工人最后一次成功读到数据的时间varlast=_lastSuccessfulReadUtc;if(last==DateTime.MinValue)continue;// 还没读过,跳过// 算一下:现在距离最后一次成功读,过了多久?varage=DateTime.UtcNow-last;// 如果:没过超时时间 → 不管// 如果:过了超时时间,并且之前没报错过 → 判定【掉线了】if(!_heartbeatLost&&age.TotalMilliseconds>HeartbeatTimeoutMs){_heartbeatLost=true;// 标记:掉线了try{OnHeartbeatLost?.Invoke();}catch{}// 通知外面:掉线了// 如果开了【自动重连】→ 开始自救if(AutoReconnect){try{Disconnect();}catch{}// 先彻底断开(清工人、清串口)// 开始重试:最多试 ReconnectMaxAttempts 次for(inti=0;i<ReconnectMaxAttempts&&!token.IsCancellationRequested;i++){try{// 重试间隔:指数退避(100ms → 200ms → 400ms → 800ms...)Thread.Sleep(ReconnectBaseDelayMs*(int)Math.Pow(2,i));// 重新打开串口if(OpenSerial(_lastPortName,_lastBaudRate)){// 重建Modbus_modbusMaster=ModbusSerialMaster.CreateRtu(_serialPort);_modbusMaster.Transport.Retries=1;_modbusMaster.Transport.WaitToRetryMilliseconds=50;break;// 重连成功,跳出循环}}catch{// 失败了不管,继续重试}}}}}}awaitTask.Delay(Math.Max(200,HeartbeatIntervalMs),token).ConfigureAwait(false);我用最通俗、最扎心的大白话告诉你:
监工必须休息!不休息,你的电脑CPU会直接炸掉,软件卡死动不了!
结合你这行代码,逐点给你讲死👇
awaitTask.Delay(Math.Max(200,HeartbeatIntervalMs),token).ConfigureAwait(false);1. 监工不休息会发生什么?(恐怖后果)
监工的工作是循环检查设备有没有掉线:
while(!token.IsCancellationRequested){// 检查掉线...}如果没有这行 Delay 休息:
- 监工会一秒钟循环几万次,疯狂检查
- 电脑CPU直接占满(100%)
- 软件界面卡死、点不动
- 设备发热、卡顿,工业现场直接报废
这就是死循环!绝对不能允许!
2. 这行代码的真正作用:让监工定时巡逻,而不是疯跑
Task.Delay(时间)=监工歇一会儿,再去检查- 不是偷懒,是工业软件的保命写法
- 比如你设置心跳间隔 1000ms → 监工歇1秒,检查一次
- 既不浪费CPU,又能实时监控设备状态
3. 为什么要加Math.Max(200, ...)?(双重保险)
- 防止你把心跳间隔设太小(比如10ms)
- 强制最少休息200毫秒
- 彻底杜绝监工疯跑、CPU爆满
4. 为什么要带token?
- 监工休息的时候,也能听见工会代表的停工命令
- 一喊停,立刻停止休息,直接下班
- 不耽误线程退出
终极大白话总结
监工休息 = 为了不浪费电脑资源,不让软件卡死!
定时歇一会儿,再去巡逻检查,这是工业监控软件的标准规矩!
这行代码不是多余的,是保命的!
这里的 token 是干嘛的?(停工保命用)
大白话翻译:
监工休息的时候,手里必须攥着「停工令牌」!
核心作用:
监工正在休息(Delay)
如果你关闭窗口、调用 Disconnect() → 工会代表(_cts)喊 停工!
这个 token 会立刻打断休息,让监工马上停止循环、下班走人
如果没有 token:监工休息到一半,你喊停工他听不见,会一直赖在后台,变成僵尸线程,导致串口占用、软件卡死
极简记忆:
token = 监工休息时的「紧急停工按钮」
二、.ConfigureAwait(false) 是干嘛的?(界面流畅用)
大白话翻译:
监工休息完,就在后台自己干活,别来打扰界面(窗口)!
核心作用(工业软件必加):
你的软件有窗口界面(主线程),还有后台监工 / 工人(子线程)
默认情况下,后台任务休息完会跑回界面线程,抢资源
加了 .ConfigureAwait(false):
监工全程在后台后台干活
不抢界面资源
窗口绝不卡顿、绝不假死
极简记忆:
ConfigureAwait (false) = 后台任务不打扰界面,保证软件流畅不卡
catch (OperationCanceledException) { break; }
核心大白话翻译
监工收到「强制停工通知」了!立刻结束休息、退出循环、下班走人!
完整场景(结合上面的 Delay 代码)
监工正在休息 await Task.Delay(…)
你关闭窗口 / 调用 Disconnect() → 工会代表喊 Cancel() 停工
手里的 token 立刻生效,强行打断休息
系统会抛出 OperationCanceledException(取消操作异常)
catch 抓住这个异常 → 执行 break
直接跳出监工的死循环,监工彻底下班
它是干嘛用的?(必须要写)
不写这行:监工会因为被打断休息而报错崩溃
写了这行:优雅停工,不报错、不残留、不占用串口资源
工业软件必须这么写,保证线程安全退出
var age = DateTime.UtcNow - last;
计算:从「工人最后一次成功读到数据」到「现在」,一共过去了多长时间!
计算:从「工人最后一次成功读到数据」到「现在」,一共过去了多长时间!
_heartbeatLost
作用:标记设备是否已经掉线、是否已经触发过报警和重连
age.TotalMilliseconds
刚才算出来的 → 工人多久没读到数据了(总毫秒数)
HeartbeatTimeoutMs
你设定的 心跳超时时间(比如 3000 毫秒 = 3 秒)
→ 这是底线:超过这个时间没数据,就算掉线!
工人摸鱼 / 断连的时间,超过了我设定的最大容忍时间 → 判定设备掉线!
AutoReconnect
if(AutoReconnect){try{Disconnect();}catch{}for(inti=0;i<ReconnectMaxAttempts&&!token.IsCancellationRequested;i++){try{Thread.Sleep(ReconnectBaseDelayMs*(int)Math.Pow(2,i));if(OpenSerial(_lastPortName,_lastBaudRate)){_modbusMaster=ModbusSerialMaster.CreateRtu(_serialPort);_modbusMaster.Transport.Retries=1;_modbusMaster.Transport.WaitToRetryMilliseconds=50;break;}}catch{}}1. if (AutoReconnect)
→ 你允许监工自动救场吗?
允许 = 继续执行重连
不允许 = 掉线就不管了
2. try { Disconnect(); } catch { }
→ 第一步:彻底清场!
调用你写的满分 Disconnect():
工会代表喊停工 → 工人 / 监工全部安全下班 → 关闭串口 → 销毁所有资源
必须先清场,才能重连,防止线程打架、端口占用!
3. for (int i = 0; i < 最大重试次数 && !token.IsCancellationRequested; i++)
→ 循环重试,守两个规矩:
最多试 ReconnectMaxAttempts 次(不无限死磕)
工会代表喊停工,立刻停止重试(不顽固)
4. Thread.Sleep(基础延迟 * 2^i)
→ 指数退避重试(工业核心!)
第一次等 100ms
第二次等 200ms
第三次等 400ms
第四次等 800ms
…
越重试等越久,不疯狂占用 CPU,不刷屏攻击串口
5. OpenSerial(串口名, 波特率)
→ 尝试重新打开串口工位
打开成功 = 硬件恢复连接
失败 = 继续下一次重试
6. 重建 _modbusMaster
→ 串口打开成功 → 拿新的 Modbus 通信工具
配置好参数,准备让新工人上岗读数据
7. break
→ 重连成功!结束重试循环!
终极总结(这套逻辑为什么稳?)
先清场,再重连:绝对不线程冲突
有限次数重试:不卡死软件
指数退避:不浪费 CPU 资源
随时响应停工命令:安全退出
全程 try-catch:绝不崩溃
