这样代码太复杂了,是不是实际上一个进程中同时读写的情况很少?

比如打开窗体加载一次PLC数据。然后每5秒读取一次数据。

只有一个窗口他要每秒读一次数据。其他就是输入文本框后回车写入PLC

采用队列之后,使用体验完全没影响,同时数据也安全了?


单连接模式:整个程序只创建 1 个TcpClient连接到 PLC,避免多连接抢占 PLC 资源;

并发队列:ConcurrentQueue是线程安全的队列,所有线程的读写请求都通过队列提交,避免直接操作 PLC;

调度线程:唯一的线程负责从队列取请求、与 PLC 通讯,确保同一时间只有 1 个请求发给 PLC,杜绝读写冲突;

频率控制:通过Thread.Sleep限制请求频率,避免 PLC 缓冲区溢出(建议读频率≥100ms,写频率≥500ms)。

为什么要做成线程队列?

如果你只是一个简单的上位机界面,只有“一读一写”,那么采用队列确实显得代码量激增。

但工业通讯之所以推荐**“任务队列”**,核心目的不是为了让代码看起来高级,而是为了解决 Modbus TCP 通道(Socket)的排他性。


1. 为什么要做成线程队列?(解决“打架”问题)

在你的场景中(每 5 秒读一次,偶尔回车写一次),虽然看起来请求很少,但由于 Windows 是多线程运行环境,“冲突”往往发生在毫秒级:

  • 定时读线程:每 5 秒触发一次。

  • 用户写操作:用户输入完按回车,瞬间触发写入。

如果这两者恰好在同一时刻执行:

  1. 读线程刚把“我要读 D100”发出去。

  2. 写线程立刻把“我要写 D200”塞进了同一个 Socket 通道。

  3. 结果: PLC 的回复数据会产生混乱(粘包或错序),或者导致 TcpClient 直接抛出 System.IO.IOException。一旦 Socket 报错,你的通讯可能就会彻底卡死,必须重启程序。


2. 只有单窗口请求,真的很少见吗?

确实,你的当前需求很简单,但在工业开发中,情况很快会变得复杂:

  • 逻辑分层:你可能有一个线程负责“实时显示”,另一个线程负责“后台记录数据库”,还有一个按钮负责“紧急停止”。

  • 网络波动:如果 PLC 响应慢了 100ms,而你没有队列,新的请求会直接叠加上去,导致连接崩溃。

做成队列后,就像银行的柜台排队: 无论外面有多少客户(线程),窗口只有一个。柜员(调度线程)一个接一个处理,保证了交易永远不会乱套。


3. 采用队列后,使用体验和安全性

你的总结非常精准:“使用体验完全没影响,同时数据也安全了。”

维度不加队列 (直接调用)采用队列 (Manager 模式)
UI 响应读写时界面可能微卡 (卡顿)界面瞬间返回,数据在后台异步处理
多线程安全两个线程同时写会直接 crash完美排队,互不干扰
断线重连每个读写的地方都要写 try-catch只在调度线程处理一处,全局生效
调试难度随机崩溃,极难定位逻辑清晰,所有日志都在调度中心

4. 关键体验建议:回调机制

你可能会担心:“我把请求塞进队列了,怎么知道写成功了没?怎么拿回读取的数据?”

这就是你在代码中定义的 Action<ushort[], string> callback 的威力:

  1. 用户点击“写入”。

  2. UI 线程调用 WriteData,把数据和“一段逻辑”塞进队列。

  3. UI 线程立刻去忙别的(不卡顿)。

  4. 调度线程执行完写入,回头调用那段逻辑,告诉 UI:“写好了,请更新界面”。


💡 总结你的情况

既然你现在只是单窗口、低频率:

  • 如果你想省事:可以不写队列,但在每次读写前加一个 lock(_lockObj),确保同一时间只有一个线程在用 _modbusMaster。

  • 如果你想专业:保持你现在的队列架构。因为当你以后增加“自动报警轮询”、“数据曲线记录”功能时,这个架构会让你省掉无数次由于“通讯超时”引发的加班。

更多推荐