生产者和消费者实际案例

PLC通信引擎,负责生产者消费者调度

 
  1 public sealed class PlcCommunicationEngine : IDisposable // PLC通信引擎,负责生产者消费者调度
  2 { // 类型开始
  3     private readonly ConcurrentQueue<PlcDataPacket> _dataQueue = new(); // 线程安全队列,缓存采集数据包
  4     private readonly SemaphoreSlim _dataAvailableSemaphore = new(0); // 数据到达信号量,0表示初始无数据
  5     private readonly Func<CancellationToken, Task<PlcDataPacket>> _readFunc; // 外部注入的PLC读取函数
  6     private readonly Action<PlcDataPacket>? _consumerAction; // 外部注入的消费处理动作
  7     private readonly MachineViewModel? _viewModel; // 可选视图模型,用于同步最新数据
  8 
  9     private readonly object _heartbeatLock = new(); // 心跳状态锁,避免多线程并发读写
 10     private readonly int _heartbeatDIndex; // 心跳使用的D索引(D0=>0)
 11     private readonly TimeSpan _heartbeatStaleThreshold; // 心跳超时阈值:超过则认为异常
 12     private readonly TimeSpan _heartbeatCheckInterval; // 心跳后台监测轮询间隔
 13 
 14     private short? _lastHeartbeatValue; // 上一次观测到的心跳值
 15     private DateTime? _lastHeartbeatChangeTime; // 心跳最后变化时间(来自packet.Timestamp)
 16     private bool? _lastHeartbeatHealthy; // 上一次判定的健康状态(用于避免重复事件)
 17 
 18     private CancellationTokenSource? _cts; // 任务取消源
 19     private Task? _producerTask; // 生产者任务句柄
 20     private Task? _consumerTask; // 消费者任务句柄
 21     private Task? _heartbeatMonitorTask; // 心跳后台监测任务句柄
 22     private bool _disposed; // 资源释放标志位
 23 
 24     public int MaxQueueSize { get; init; } = 100; // 队列最大长度,防止堆积过多
 25     public TimeSpan ReadInterval { get; init; } = TimeSpan.FromMilliseconds(500); // 生产者读取周期
 26     public TimeSpan ProcessInterval { get; init; } = TimeSpan.FromMilliseconds(50); // 消费者处理节流周期
 27     public bool IsRunning { get; private set; } // 引擎运行状态
 28 
 29     public event Action<PlcDataPacket>? PacketProduced; // 生产成功事件
 30     public event Action<PlcDataPacket>? PacketConsumed; // 消费成功事件
 31     public event Action<Exception>? ErrorOccurred; // 异常上报事件
 32 
 33     public event Action<HeartbeatStatusSnapshot>? HeartbeatStatusChanged; // 心跳健康状态变化事件
 34 
 35     public readonly struct HeartbeatStatusSnapshot // 心跳状态快照
 36     {
 37         public HeartbeatStatusSnapshot(short? value, DateTime? lastChangeTime, bool isHealthy, TimeSpan stale)
 38         {
 39             Value = value;
 40             LastChangeTime = lastChangeTime;
 41             IsHealthy = isHealthy;
 42             Stale = stale;
 43         }
 44 
 45         public short? Value { get; }
 46         public DateTime? LastChangeTime { get; }
 47         public bool IsHealthy { get; }
 48         public TimeSpan Stale { get; }
 49     }
 50 
 51     public PlcCommunicationEngine( // 构造函数开始
 52         Func<CancellationToken, Task<PlcDataPacket>> readFunc, // 注入读取委托
 53         Action<PlcDataPacket>? consumerAction = null, // 注入可选消费委托
 54         MachineViewModel? viewModel = null, // 注入可选视图模型
 55         int heartbeatDIndex = 0, // 心跳使用D索引(D0=>0)
 56         TimeSpan? heartbeatStaleThreshold = null, // 心跳超时阈值:超过则认为异常
 57         TimeSpan? heartbeatCheckInterval = null) // 心跳后台监测轮询间隔
 58     { // 构造函数体开始
 59         _readFunc = readFunc ?? throw new ArgumentNullException(nameof(readFunc)); // 校验并保存读取委托
 60         _consumerAction = consumerAction; // 保存消费委托
 61         _viewModel = viewModel; // 保存视图模型
 62 
 63         _heartbeatDIndex = heartbeatDIndex; // 保存心跳D索引
 64         _heartbeatStaleThreshold = heartbeatStaleThreshold ?? TimeSpan.FromSeconds(10); // 默认10秒超时
 65         _heartbeatCheckInterval = heartbeatCheckInterval ?? TimeSpan.FromMilliseconds(500); // 默认500ms轮询
 66     } // 构造函数结束
 67 
 68     public void Start() // 启动引擎
 69     { // Start方法体开始
 70         ThrowIfDisposed(); // 已释放则抛异常
 71         if (IsRunning) return; // 正在运行则直接返回
 72 
 73         _cts = new CancellationTokenSource(); // 创建新的取消源
 74         IsRunning = true; // 标记为运行中
 75 
 76         _producerTask = Task.Run(() => ProducerLoop(_cts.Token), _cts.Token); // 启动生产者后台任务
 77         _consumerTask = Task.Run(() => ConsumerLoop(_cts.Token), _cts.Token); // 启动消费者后台任务
 78         _heartbeatMonitorTask = Task.Run(() => HeartbeatMonitorLoop(_cts.Token), _cts.Token); // 启动心跳后台监测任务
 79     } // Start方法结束
 80 
 81     public async Task StopAsync() // 异步停止引擎
 82     { // StopAsync方法体开始
 83         if (!IsRunning) return; // 未运行则无需停止
 84         if (_cts is null) return; // 取消源为空则直接返回
 85 
 86         _cts.Cancel(); // 通知所有后台任务取消
 87 
 88         try // 进入等待任务结束阶段
 89         { // try块开始
 90             if (_producerTask is not null) // 如果生产者任务存在
 91             { // if块开始
 92                 await _producerTask.ConfigureAwait(false); // 等待生产者任务退出
 93             } // if块结束
 94             if (_consumerTask is not null) // 如果消费者任务存在
 95             { // if块开始
 96                 await _consumerTask.ConfigureAwait(false); // 等待消费者任务退出
 97             } // if块结束
 98 
 99             if (_heartbeatMonitorTask is not null) // 如果心跳后台监测任务存在
100             { // if块开始
101                 await _heartbeatMonitorTask.ConfigureAwait(false); // 等待心跳监测任务退出
102             } // if块结束
103         } // try块结束
104         catch (OperationCanceledException) // 捕获取消导致的正常异常
105         { // catch块开始
106             // normal cancellation flow // 这是正常取消流程,无需额外处理
107         } // catch块结束
108         finally // 无论如何都执行清理
109         { // finally块开始
110             IsRunning = false; // 复位运行状态
111             _cts.Dispose(); // 释放取消源
112             _cts = null; // 清空取消源引用
113             _producerTask = null; // 清空生产者任务引用
114             _consumerTask = null; // 清空消费者任务引用
115             _heartbeatMonitorTask = null; // 清空心跳监测任务引用
116         } // finally块结束
117     } // StopAsync方法结束
118 
119     private async Task ProducerLoop(CancellationToken token) // 生产者循环:读取PLC并入队
120     { // ProducerLoop方法体开始
121         while (!token.IsCancellationRequested && IsRunning) // 在未取消且运行中时循环
122         { // while块开始
123             try // 尝试一次采集流程
124             { // try块开始
125                 PlcDataPacket packet = await _readFunc(token).ConfigureAwait(false); // 调用读取委托获取数据包
126 
127                 if (_dataQueue.Count >= MaxQueueSize) // 如果队列达到上限
128                 { // if块开始
129                     _dataQueue.TryDequeue(out _); // 丢弃最旧一条数据,腾出空间
130                 } // if块结束
131 
132                 _dataQueue.Enqueue(packet); // 新数据入队
133                 _dataAvailableSemaphore.Release(); // 释放信号量通知消费者
134                 PacketProduced?.Invoke(packet); // 触发生产事件
135 
136                 await Task.Delay(ReadInterval, token).ConfigureAwait(false); // 按配置间隔采样
137             } // try块结束
138             catch (OperationCanceledException) // 捕获取消异常
139             { // catch块开始
140                 break; // 收到取消则退出循环
141             } // catch块结束
142             catch (Exception ex) // 捕获非取消异常
143             { // catch块开始
144                 ErrorOccurred?.Invoke(ex); // 上报异常
145                 await Task.Delay(2000, token).ConfigureAwait(false); // 异常后降频等待再重试
146             } // catch块结束
147         } // while块结束
148     } // ProducerLoop方法结束
149 
150     private async Task ConsumerLoop(CancellationToken token) // 消费者循环:等待信号并处理数据
151     { // ConsumerLoop方法体开始
152         while (!token.IsCancellationRequested) // 未取消时持续消费
153         { // while块开始
154             try // 尝试一次消费流程
155             { // try块开始
156                 await _dataAvailableSemaphore.WaitAsync(token).ConfigureAwait(false); // 等待至少一条数据可用
157 
158                 while (_dataQueue.TryDequeue(out PlcDataPacket? packet)) // 批量把队列里当前数据全部取出
159                 { // 内层while开始
160                     _consumerAction?.Invoke(packet); // 调用外部消费逻辑
161                     _viewModel?.Update(packet); // 更新视图模型
162                     PacketConsumed?.Invoke(packet); // 触发消费完成事件
163 
164                     UpdateHeartbeat(packet); // 更新心跳健康状态(按packet.Timestamp判断是否超时)
165                 } // 内层while结束
166 
167                 await Task.Delay(ProcessInterval, token).ConfigureAwait(false); // 按配置做轻量节流
168             } // try块结束
169             catch (OperationCanceledException) // 捕获取消异常
170             { // catch块开始
171                 break; // 取消时退出循环
172             } // catch块结束
173             catch (Exception ex) // 捕获处理异常
174             { // catch块开始
175                 ErrorOccurred?.Invoke(ex); // 上报异常,不中断总循环
176             } // catch块结束
177         } // while块结束
178     } // ConsumerLoop方法结束
179 
180     public void Dispose() // 释放资源
181     { // Dispose方法体开始
182         if (_disposed) return; // 避免重复释放
183         _disposed = true; // 标记已释放
184 
185         StopAsync().GetAwaiter().GetResult(); // 同步等待后台任务停止
186         _dataAvailableSemaphore.Dispose(); // 释放信号量资源
187     } // Dispose方法结束
188 
189     private void ThrowIfDisposed() // 已释放检查辅助方法
190     { // ThrowIfDisposed方法体开始
191         if (_disposed) // 如果已经释放
192         { // if块开始
193             throw new ObjectDisposedException(nameof(PlcCommunicationEngine)); // 抛出标准对象释放异常
194         } // if块结束
195     } // ThrowIfDisposed方法结束
196 
197     private void UpdateHeartbeat(PlcDataPacket packet) // 更新心跳:检测D点值变化,并计算是否超时
198     { // UpdateHeartbeat方法体开始
199         DateTime packetTime = packet.Timestamp; // 当前数据包采样时间戳
200         short? current = null; // 当前心跳值(缺失时为null)
201 
202         if (packet.DValues.Length > _heartbeatDIndex) // 心跳索引在DValues范围内
203         { // if块开始
204             current = packet.DValues[_heartbeatDIndex]; // 取心跳点当前值
205         } // if块结束
206 
207         HeartbeatStatusSnapshot snapshot; // 心跳状态快照
208         bool shouldNotify = false; // 是否需要通知外部
209 
210         lock (_heartbeatLock) // 锁定,避免和监控任务并发修改
211         { // lock开始
212             if (_lastHeartbeatValue is null) // 首次初始化
213             { // if块开始
214                 _lastHeartbeatValue = current; // 初始化心跳值
215                 _lastHeartbeatChangeTime = packetTime; // 初始化最后变化时间
216             } // if块结束
217             else if (_lastHeartbeatValue.Value != current) // 值发生变化
218             { // else-if块开始
219                 _lastHeartbeatValue = current; // 更新心跳值
220                 _lastHeartbeatChangeTime = packetTime; // 记录最后变化时间
221             } // else-if块结束
222 
223             TimeSpan stale = _lastHeartbeatChangeTime is null ? TimeSpan.MaxValue : DateTime.Now - _lastHeartbeatChangeTime.Value; // 心跳超时时间
224             bool isHealthy = _lastHeartbeatChangeTime is not null && stale <= _heartbeatStaleThreshold; // 当前健康判定
225 
226             snapshot = new HeartbeatStatusSnapshot(_lastHeartbeatValue, _lastHeartbeatChangeTime, isHealthy, stale); // 生成快照
227 
228             if (_lastHeartbeatHealthy is null || _lastHeartbeatHealthy.Value != isHealthy) // 状态发生变化才通知
229             { // if块开始
230                 _lastHeartbeatHealthy = isHealthy; // 更新最近一次健康状态
231                 shouldNotify = true; // 标记需要通知
232             } // if块结束
233         } // lock结束
234 
235         if (shouldNotify) // 只有确实发生变化才触发事件
236         { // if块开始
237             HeartbeatStatusChanged?.Invoke(snapshot); // 通知外部心跳健康状态变化
238         } // if块结束
239     } // UpdateHeartbeat方法结束
240 
241     private async Task HeartbeatMonitorLoop(CancellationToken token) // 心跳后台监控:即使心跳值不再变化也能超时告警
242     { // HeartbeatMonitorLoop方法体开始
243         while (!token.IsCancellationRequested) // 循环直到取消
244         { // while块开始
245             try // try块开始
246             { // try块内容
247                 HeartbeatStatusSnapshot? snapshotToSend = null; // 需要发送时才赋值
248 
249                 lock (_heartbeatLock) // 锁定读取状态
250                 { // lock开始
251                     if (_lastHeartbeatChangeTime is not null) // 已初始化则可计算
252                     { // if块开始
253                         TimeSpan stale = DateTime.Now - _lastHeartbeatChangeTime.Value; // 计算当前失效时间
254                         bool isHealthy = stale <= _heartbeatStaleThreshold; // 判定是否健康
255 
256                         if (_lastHeartbeatHealthy is null || _lastHeartbeatHealthy.Value != isHealthy) // 状态变化才通知
257                         { // if块开始
258                             _lastHeartbeatHealthy = isHealthy; // 更新缓存
259                             snapshotToSend = new HeartbeatStatusSnapshot(_lastHeartbeatValue, _lastHeartbeatChangeTime, isHealthy, stale); // 生成快照
260                         } // if块结束
261                     } // if块结束
262                 } // lock结束
263 
264                 if (snapshotToSend is not null) // 有变化
265                 { // if块开始
266                     HeartbeatStatusChanged?.Invoke(snapshotToSend.Value); // 触发事件
267                 } // if块结束
268             } // try块结束
269             catch // 忽略心跳监控异常,避免监控任务中断
270             { // catch块开始
271             } // catch块结束
272 
273             await Task.Delay(_heartbeatCheckInterval, token).ConfigureAwait(false); // 按轮询间隔等待
274         } // while块结束
275     } // HeartbeatMonitorLoop方法结束
276 } // 类型结束

PLC数据结构

1 public sealed class PlcDataPacket
2 {
3     public DateTime Timestamp { get; init; } = DateTime.Now;
4     public short[] DValues { get; init; } = Array.Empty<short>();
5     public bool[] XValues { get; init; } = Array.Empty<bool>();
6 }

UI层数据-----观察者

public sealed class MachineViewModel
{
    public PlcDataPacket? LastPacket { get; private set; }

    public void Update(PlcDataPacket packet)
    {
        LastPacket = packet;
    }
}

 

posted on 2026-03-27 16:57  工业搬砖猿Lee  阅读(11)  评论(0)    收藏  举报