生产者和消费者实际案例
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; } }
浙公网安备 33010602011771号