C#在工业自动化上位机中,如何使用观察者和消费者模式

一、先明确两个模式在上位机里的作用

1. 观察者模式(Observer)

用途:数据变化时,自动通知 UI、报警、日志、曲线等多个模块更新。
典型场景:
  • PLC 数据刷新 → 界面显示
  • 温度超标 → 触发报警
  • 数据变化 → 写入日志
  • 数据变化 → 曲线控件更新
一句话:一个数据源变了,N 个地方自动跟着变。

2. 生产者 - 消费者模式(Producer-Consumer)

用途:解耦 “数据采集” 和 “数据处理”,防止界面卡顿、数据丢失、线程爆炸。
典型场景:
  • PLC 高频采集(10ms 一次)
  • 数据解析、入库、日志写入慢
  • 不能阻塞采集线程
  • 不能丢包
一句话:生产只管扔数据,消费只管慢慢处理,互不干扰。
 

二、场景设定(真实上位机)

假设我们做一个温控设备上位机:
 
  1. 定时从 Modbus/TCP 或 PLC 读取温度、压力
  2. 数据到达后:
    • 更新主界面显示
    • 判断是否报警
    • 写入日志
    • 存入数据库
     
  3. 采集高频(100ms),处理较慢,必须用生产者 - 消费者
  4. 数据变化要通知多个模块 → 观察者模式

三、第一步:定义数据实体(DeviceData)

 1 // 设备数据
 2 public class DeviceData
 3 {
 4     // 温度
 5     public double Temperature { get; set; }
 6     // 压力
 7     public double Pressure { get; set; }
 8     // 采集时间
 9     public DateTime CollectTime { get; set; }
10 }

四、观察者模式实现(数据主题 + 多个观察者)

1. 抽象观察者接口

1 public interface IDataObserver
2 {
3     // 数据更新时被调用
4     void OnDataUpdate(DeviceData data);
5 }

2. 主题(被观察者:数据中心)

 1 public class DataSubject
 2 {
 3     // 保存所有观察者
 4     private readonly List<IDataObserver> _observers = new List<IDataObserver>();
 5 
 6     // 注册观察者
 7     public void Attach(IDataObserver observer)
 8     {
 9         _observers.Add(observer);
10     }
11 
12     // 移除观察者
13     public void Detach(IDataObserver observer)
14     {
15         _observers.Remove(observer);
16     }
17 
18     // 通知所有观察者
19     public void Notify(DeviceData data)
20     {
21         foreach (var observer in _observers)
22         {
23             observer.OnDataUpdate(data);
24         }
25     }
26 }

3. 具体观察者 A:UI 界面更新

 1 public class UiDisplayObserver : IDataObserver
 2 {
 3     public void OnDataUpdate(DeviceData data)
 4     {
 5         // 真实项目中使用 Dispatcher 切换到 UI 线程
 6         Application.Current.Dispatcher.Invoke(() =>
 7         {
 8             Console.WriteLine($"【UI更新】温度:{data.Temperature} ℃");
 9         });
10     }
11 }

4. 具体观察者 B:报警监测

 1 public class AlarmObserver : IDataObserver
 2 {
 3     public void OnDataUpdate(DeviceData data)
 4     {
 5         if (data.Temperature > 60)
 6         {
 7             Console.WriteLine($"【报警】温度过高!当前:{data.Temperature}");
 8         }
 9     }
10 }

5. 具体观察者 C:日志记录

1 public class LogObserver : IDataObserver
2 {
3     public void OnDataUpdate(DeviceData data)
4     {
5         Console.WriteLine($"【日志】{data.CollectTime} T={data.Temperature} P={data.Pressure}");
6     }
7 }

五、生产者 - 消费者模式(处理高频采集)

工业上位机必须用这个,否则高频采集会卡死 UI。
 
这里用 BlockingCollection<T>(线程安全队列)。
 1 using System.Collections.Concurrent;
 2 
 3 public class DataQueue
 4 {
 5     // 阻塞队列,线程安全
 6     private readonly BlockingCollection<DeviceData> _queue = new BlockingCollection<DeviceData>();
 7 
 8     // 生产者:扔数据进入队列
 9     public void Produce(DeviceData data)
10     {
11         _queue.Add(data);
12     }
13 
14     // 消费者:后台持续处理
15     public void StartConsume(DataSubject subject)
16     {
17         Task.Run(() =>
18         {
19             while (!_queue.IsCompleted)
20             {
21                 // 没有数据会自动阻塞,不占CPU
22                 if (_queue.TryTake(out var data, -1))
23                 {
24                     // 数据解析完成 → 通知所有观察者
25                     subject.Notify(data);
26                 }
27             }
28         });
29     }
30 }

六、模拟 PLC 数据采集(生产者)

 1 public class PlcCollector
 2 {
 3     private readonly DataQueue _dataQueue;
 4     private Timer _timer;
 5 
 6     public PlcCollector(DataQueue queue)
 7     {
 8         _dataQueue = queue;
 9     }
10 
11     // 开始采集
12     public void StartCollect()
13     {
14         // 100ms 采集一次(模拟高频)
15         _timer = new Timer(CollectCallback, null, 0, 100);
16     }
17 
18     private void CollectCallback(object state)
19     {
20         // 模拟从 PLC 读取数据
21         var data = new DeviceData
22         {
23             Temperature = new Random().Next(40, 80),
24             Pressure = new Random().Next(100, 200),
25             CollectTime = DateTime.Now
26         };
27 
28         // 扔给队列,不做任何处理
29         _dataQueue.Produce(data);
30     }
31 }

七、上位机主程序入口(组装整个系统)

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         // 1. 创建主题
 6         var subject = new DataSubject();
 7 
 8         // 2. 注册观察者
 9         subject.Attach(new UiDisplayObserver());
10         subject.Attach(new AlarmObserver());
11         subject.Attach(new LogObserver());
12 
13         // 3. 创建队列
14         var dataQueue = new DataQueue();
15 
16         // 4. 启动消费者
17         dataQueue.StartConsume(subject);
18 
19         // 5. 启动 PLC 采集(生产者)
20         var collector = new PlcCollector(dataQueue);
21         collector.StartCollect();
22 
23         Console.ReadLine();
24     }
25 }

八、运行结果(典型工业上位机逻辑)

1 【UI更新】温度:582 【日志】2025-08-23 15:30:20 T=58 P=122
3 【UI更新】温度:654 【报警】温度过高!当前:65
5 【日志】2025-08-23 15:30:21 T=65 P=145

九、为什么工业上位机必须这么写?(核心价值)

1. 观察者模式好处

  • 采集线程只负责采集,不关心谁要显示、谁要报警
  • 新增功能(如曲线、数据库)不用改采集代码
  • 低耦合、易扩展、符合上位机软件架构规范

2. 生产者 - 消费者好处

  • 采集 100ms 极快,处理再慢也不阻塞采集
  • 不会丢数据
  • 不会开一堆线程导致系统崩溃
  • 界面永远不卡顿

3. 架构优势

  • 采集层 ↔ 队列 ↔ 解析层 ↔ 业务层 ↔ UI
     
    完全分层,标准工业软件架构。

十、对应 WPF 上位机怎么改?

只需要把观察者中的:
1 Application.Current.Dispatcher.Invoke(() => {
2     // 更新绑定的 ViewModel 属性
3     vm.Temp = data.Temperature;
4 });

即可实现MVVM + 观察者 + 生产消费的标准工业上位机架构。

十一、一句话总结

  • 观察者模式 = 数据一变,多处自动更新(UI / 报警 / 日志)
  • 生产者 - 消费者 = 采集与处理分离,保证高速稳定不卡顿
这是几乎所有 C# 工业自动化上位机的标准底层架构。
posted on 2026-03-27 15:03  工业搬砖猿Lee  阅读(67)  评论(0)    收藏  举报