plc多线程读写数据为什么要做成线程队列
这样代码太复杂了,是不是实际上一个进程中同时读写的情况很少?
比如打开窗体加载一次PLC数据。然后每5秒读取一次数据。
只有一个窗口他要每秒读一次数据。其他就是输入文本框后回车写入PLC
采用队列之后,使用体验完全没影响,同时数据也安全了?
单连接模式:整个程序只创建 1 个TcpClient连接到 PLC,避免多连接抢占 PLC 资源;
并发队列:ConcurrentQueue是线程安全的队列,所有线程的读写请求都通过队列提交,避免直接操作 PLC;
调度线程:唯一的线程负责从队列取请求、与 PLC 通讯,确保同一时间只有 1 个请求发给 PLC,杜绝读写冲突;
频率控制:通过Thread.Sleep限制请求频率,避免 PLC 缓冲区溢出(建议读频率≥100ms,写频率≥500ms)。
为什么要做成线程队列?
如果你只是一个简单的上位机界面,只有“一读一写”,那么采用队列确实显得代码量激增。
但工业通讯之所以推荐**“任务队列”**,核心目的不是为了让代码看起来高级,而是为了解决 Modbus TCP 通道(Socket)的排他性。
1. 为什么要做成线程队列?(解决“打架”问题)
在你的场景中(每 5 秒读一次,偶尔回车写一次),虽然看起来请求很少,但由于 Windows 是多线程运行环境,“冲突”往往发生在毫秒级:
-
定时读线程:每 5 秒触发一次。
-
用户写操作:用户输入完按回车,瞬间触发写入。
如果这两者恰好在同一时刻执行:
-
读线程刚把“我要读 D100”发出去。
-
写线程立刻把“我要写 D200”塞进了同一个 Socket 通道。
-
结果: PLC 的回复数据会产生混乱(粘包或错序),或者导致
TcpClient直接抛出System.IO.IOException。一旦 Socket 报错,你的通讯可能就会彻底卡死,必须重启程序。
2. 只有单窗口请求,真的很少见吗?
确实,你的当前需求很简单,但在工业开发中,情况很快会变得复杂:
-
逻辑分层:你可能有一个线程负责“实时显示”,另一个线程负责“后台记录数据库”,还有一个按钮负责“紧急停止”。
-
网络波动:如果 PLC 响应慢了 100ms,而你没有队列,新的请求会直接叠加上去,导致连接崩溃。
做成队列后,就像银行的柜台排队: 无论外面有多少客户(线程),窗口只有一个。柜员(调度线程)一个接一个处理,保证了交易永远不会乱套。
3. 采用队列后,使用体验和安全性
你的总结非常精准:“使用体验完全没影响,同时数据也安全了。”
| 维度 | 不加队列 (直接调用) | 采用队列 (Manager 模式) |
| UI 响应 | 读写时界面可能微卡 (卡顿) | 界面瞬间返回,数据在后台异步处理 |
| 多线程安全 | 两个线程同时写会直接 crash | 完美排队,互不干扰 |
| 断线重连 | 每个读写的地方都要写 try-catch | 只在调度线程处理一处,全局生效 |
| 调试难度 | 随机崩溃,极难定位 | 逻辑清晰,所有日志都在调度中心 |
4. 关键体验建议:回调机制
你可能会担心:“我把请求塞进队列了,怎么知道写成功了没?怎么拿回读取的数据?”
这就是你在代码中定义的 Action<ushort[], string> callback 的威力:
-
用户点击“写入”。
-
UI 线程调用
WriteData,把数据和“一段逻辑”塞进队列。 -
UI 线程立刻去忙别的(不卡顿)。
-
调度线程执行完写入,回头调用那段逻辑,告诉 UI:“写好了,请更新界面”。
💡 总结你的情况
既然你现在只是单窗口、低频率:
-
如果你想省事:可以不写队列,但在每次读写前加一个
lock(_lockObj),确保同一时间只有一个线程在用_modbusMaster。 -
如果你想专业:保持你现在的队列架构。因为当你以后增加“自动报警轮询”、“数据曲线记录”功能时,这个架构会让你省掉无数次由于“通讯超时”引发的加班。
更多推荐
所有评论(0)