.NET工控概念科普——工控的"实时"和Web的"实时"不是一回事

前言

大家好,我是wacky。上四篇我们画完了.NET工控的生态地图------通信协议、数据存储、业务逻辑、界面展示,一条完整的技术栈链路。从这一篇开始,我们换个角度:不聊技术栈,聊聊一些工控的基本概念。这些概念看起来"虚",但它们直接决定你做架构决策时的判断力。

第一个要聊的概念是"实时"。如果你做过Web开发,你对"实时"的理解大概是WebSocket推送、SignalR长连接、消息队列的毫秒级延迟。但在工控领域,"实时"这个词的含义完全不同------它不是"快",而是"确定"。

Web的"实时":够快就行

先明确一下Web开发中"实时"是什么意思。你给一个Web系统发请求,服务器处理完返回结果,整个过程从几毫秒到几百毫秒不等。在用户体验层面,"实时"通常意味着"感觉不到延迟"------比如聊天消息秒推、股票行情毫秒刷新、在线协作光标跟随。技术上靠的是WebSocket、长轮询这些方案。

这种"实时"的核心特征是:快,但不保证。网络抖动一下,延迟可能从50ms跳到500ms;服务器GC一下,响应可能卡半秒。用户顶多觉得"刚才卡了一下",刷新就好了。因为Web系统的业务逻辑不依赖精确的时间------你晚0.5秒收到聊天消息,不影响任何事。

工控的"实时":必须确定

工控的"实时"不是"快",是"确定"。这不是文字游戏------"快"是平均值,"确定"是最坏情况。

举个例子。一个化工厂的反应釜,温度超过120℃必须立刻关闭加热器。从温度传感器读到120℃,到PLC输出"关闭加热器"信号,这个过程必须在确定的、可预见的时间内完成。这个时间不是"大概50ms",而是"严格不超过5ms"。

为什么"大概"不行?因为如果你的系统"大部分时候3ms完成,偶尔50ms",那偶尔的50ms发生在温度超标的那一刻,反应釜可能已经到150℃了------轻则废料,重则爆炸。

这就是工控"实时"的核心:确定性响应时间(Deterministic Response Time)。不是平均多快,而是最坏多慢。工控系统设计的时候,关注的是"这个操作最长需要多长时间"------因为最坏情况才是安全边界。

硬实时 vs 软实时

理解了"确定"这个概念,再来看工业领域对实时的分类:

硬实时(Hard Real-Time):错过截止时间意味着系统故障。上面那个反应釜的例子就是硬实时------超过5ms没响应,后果是安全事故或设备损坏。硬实时的典型场景是运动控制(伺服电机位置环在微秒级)、安全联锁(急停回路必须在几毫秒内切断)、高速数据采集。

软实时(Soft Real-Time):错过截止时间会导致性能下降但不造成事故。比如上位机界面上温度曲线的刷新------本来应该每100ms刷新一次,偶尔200ms刷新一次,操作员觉得"有点卡",但不影响安全。软实时的典型场景是HMI界面刷新、历史数据记录、报警日志。

这中间还有一个"固实时(Firm Real-Time)"的概念------错过截止时间不会造成安全事故,但数据就无效了。比如高速采集卡丢失了一个采样点,这个点永远回不来了,但不影响系统安全。

为什么PLC能做硬实时,.NET不能

聊到这里,你可能想问:.NET能做硬实时吗?

答案是:不能。不是.NET不够好,是设计目标不同。

PLC能做硬实时,因为它的运行机制是为实时控制设计的。PLC的工作方式是"扫描循环"------输入采样→程序执行→输出刷新→回到输入采样。每次扫描的时间是确定的,因为PLC不跑操作系统,没有多任务调度、没有GC、没有网络中断。它的处理器就干一件事:按固定周期跑你的控制逻辑。扫描周期可以精确到毫秒甚至微秒级,而且每次扫描的时间高度一致。

.NET运行在通用操作系统上(Windows/Linux),操作系统有几百个进程在跑、有内存管理、有磁盘IO、有网络中断。即使你把.NET程序的优先级调最高,操作系统仍然可能在你最关键的时刻做一次内存页交换,让你的代码卡住几十毫秒。这几十毫秒对Web系统无所谓,对硬实时控制就是灾难。

所以工业控制中硬实时场景的架构是:PLC做硬实时控制,上位机(.NET)做软实时------数据采集、界面展示、报警处理、历史记录。上位机不参与毫秒级的控制决策,它通过通信协议(Modbus/OPC UA)读取PLC已经处理好的数据,展示给人看、存储到数据库、做趋势分析。

这个分工意味着:.NET上位机的"实时"目标是"及时",不是"确定"。你需要在100ms内把温度显示到界面上,但偶尔200ms也可以接受。

上位机开发的"实时"技巧

虽然.NET做不了硬实时,但上位机开发中仍然有一些"实时"相关的坑需要注意。这里说两个最关键的。

第一个是轮询频率。很多工控新手上来就写一个100ms的定时器不停地读PLC数据。但如果PLC的扫描周期是20ms,你100ms读一次没问题。但如果PLC的扫描周期是200ms,你100ms读一次,读到的数据有一半是重复的------PLC还没更新呢。轮询频率要匹配PLC的扫描周期,不是越快越好。通常建议轮询周期≥2倍PLC扫描周期。

第二个是UI线程不能阻塞。工控上位机的界面卡顿,80%的原因不是"电脑太慢",是有人把耗时操作放在了UI线程上。WPF的数据绑定机制已经帮你解决了大部分问题------数据更新在后台线程完成,ViewModel通过INotifyPropertyChanged通知界面刷新。但如果你在按钮点击事件里写了一个同步的PLC通信操作,界面就会卡住。记住一条铁律:所有通信操作必须异步。

复制代码
// 错误:按钮事件里同步读PLC,界面卡死
        private void ReadButton_Click(object sender, RoutedEventArgs e)
        {
            var data = plc.ReadHoldingRegisters(0, 10); // 同步阻塞UI线程
                                                        // 空值和长度检查
            if (data == null || data.Length == 0)
            {
                TemperatureText.Text = "无数据";
                return;
            }

            TemperatureText.Text = data[0].ToString();
        }


        // 正确:异步读PLC,界面不卡
        private async void ReadButton_Click(object sender, RoutedEventArgs e)
        {
            // 防止重入:读取过程中禁用按钮
            ReadButton.IsEnabled = false;
            try
            {
                // 在后台线程读取 PLC,不阻塞 UI
                var data = await Task.Run(() => plc.ReadHoldingRegisters(0, 10));
                // 空值和长度检查
                if (data == null || data.Length == 0)
                {
                    TemperatureText.Text = "无数据";
                    return;
                }
                // await 之后自动回到 UI 线程,可以安全更新控件
                TemperatureText.Text = data[0].ToString();
            }
            catch (Exception ex)
            {
                // async void 中必须捕获所有异常,否则直接崩溃
                TemperatureText.Text = $"读取失败: {ex.Message}";
            }
            finally
            {
                ReadButton.IsEnabled = true;
            }
        }

后记

"实时"这个词在IT和OT两个世界里含义完全不同。做Web的人聊实时,说的是体验;做工控的人聊实时,说的是安全。理解了这个区别,你就理解了为什么工控系统的架构是PLC+上位机的组合,而不是一个.NET程序包办一切。

下一篇我们聊PLC的扫描周期------理解了它,才算真正入了工控的门。诸君共勉。

本文首发于我的公众号【wacky的碎碎念】,欢迎关注追更!