这种“先投料绑定器件码、中间异步测试、最后当下料位触发时根据测试结果通知PLC搬运”的场景在工业自动化控制中非常经典。使用“队列 字典”的思路完全正确。队列用来保持器件在流水线上的物理先后顺序字典用来实现根据器件码快速检索和更新测试结果。为了确保整个流程在多线程、高并发的工业现场比如扫码、测试完成、下料触发由不同PLC信号或线程驱动下绝对安全且不漏数据以下是为你设计的最优 C# 解决方案。核心设计思路统一的数据实体Data Model将器件码和测试状态封装成一个对象这样队列和字典可以共享同一个对象的引用。线程安全Thread-Safety工业现场的“放料”、“测试”、“下料”通常由不同的事件或线程触发。必须使用ConcurrentDictionary和带锁的队列或ConcurrentQueue防止数据竞争。先进先出FIFO与物理同步下料位触发时直接从队列头部Dequeue出队出来的器件就是物理上第一个到达下料位的器件。此时再去字典里查询它最终的测试状态。最优代码实现 (C#)usingSystem;usingSystem.Collections.Concurrent;usingSystem.Collections.Generic;namespaceIndustrialAutomation{// 1. 定义器件状态枚举publicenumTestResult{Running,// 测试中Pass,// 测试通过Fail// 测试失败默认初始值}// 2. 定义器件实体publicclassDeviceItem{publicstringDeviceCode{get;set;}publicTestResultStatus{get;set;}publicDateTimeTimestamp{get;set;}publicDeviceItem(stringdeviceCode){DeviceCodedeviceCode;StatusTestResult.Fail;// 放料时默认为 FailTimestampDateTime.Now;}}// 3. 核心流水线控制引擎publicclassProductionLineManager{// 线程安全的字典用于根据器件码快速更新状态privatereadonlyConcurrentDictionarystring,DeviceItem_deviceDictnew();// 线程安全的队列用于维持器件在传送带上的物理顺序privatereadonlyConcurrentQueueDeviceItem_deviceQueuenew();/// summary/// 步骤一放料位触发扫码绑定/// /summarypublicvoidOnDeviceLoaded(stringdeviceCode){if(string.IsNullOrWhiteSpace(deviceCode))return;// 创建新器件对象默认 FailvarnewItemnewDeviceItem(deviceCode);// 存入字典如果键已存在则更新正常情况不应重复_deviceDict[deviceCode]newItem;// 存入队列记录物理顺序_deviceQueue.Enqueue(newItem);Console.WriteLine($[放料完成] 器件码:{deviceCode}, 初始状态:{newItem.Status});}/// summary/// 步骤二测试位触发测试结束异步更新结果/// /summarypublicvoidOnTestCompleted(stringdeviceCode,boolisPass){// 根据器件码直接从字典中找出对应的对象引用if(_deviceDict.TryGetValue(deviceCode,outvardevice)){// 修改对象属性队列中的对应对象也会同步变更引用类型device.StatusisPass?TestResult.Pass:TestResult.Fail;Console.WriteLine($[测试完成] 器件码:{deviceCode}, 结果更新为:{device.Status});}else{// 异常处理测试触发了但系统里没有这个码Console.WriteLine($[错误] 收到未注册的器件测试结果:{deviceCode});}}/// summary/// 步骤三下料位触发PLC感应到器件到达下料点/// /summary/// returns返回 0: Fail/异常搬运, 1: Pass搬运/returnspublicintOnDeviceAtUnloadStation(){// 1. 从队列头部取出物理上第一个到达的器件if(_deviceQueue.TryDequeue(outvarcurrentDevice)){// 2. 移除字典中的缓存防止内存泄漏_deviceDict.TryRemove(currentDevice.DeviceCode,out_);Console.WriteLine($[下料触发] 检测到器件:{currentDevice.DeviceCode}, 最终状态:{currentDevice.Status});// 3. 根据最终状态返回告知PLC的控制信号// 假设1 代表 Pass 轨道0 代表 Fail 轨道returncurrentDevice.StatusTestResult.Pass?1:0;}// 异常情况PLC下料位感应到了物料但软件队列里没数据可能有人手动拿放料或传感器误报Console.WriteLine([严重错误] 下料位触发但队列中无器件数据);return0;// 安全起见当作 Fail 抛弃或报警}}}流程运作演示我们可以模拟一个标准的流水线运转过程放料 - 开始测试 - 另一个放料 - 测试结束 - 下料classProgram{staticvoidMain(){ProductionLineManagermanagernewProductionLineManager();Console.WriteLine(--- 模拟流水线开始运转 ---\n);// 1. 放入 1 号芯片manager.OnDeviceLoaded(SN_001);// 2. 放入 2 号芯片manager.OnDeviceLoaded(SN_002);// 3. 2号芯片测试速度快先结束了结果为 PASSmanager.OnTestCompleted(SN_002,true);// 4. 1号芯片测试结束结果为 PASSmanager.OnTestCompleted(SN_001,true);Console.WriteLine(\n--- 开始到达下料位 ---);// 5. 物理上传送带先到的必然是 1 号intplcSignal1manager.OnDeviceAtUnloadStation();Console.WriteLine($通知PLC信号 (1号):{plcSignal1});// 输出 1// 6. 接着 2 号到达下料位intplcSignal2manager.OnDeviceAtUnloadStation();Console.WriteLine($通知PLC信号 (2号):{plcSignal2});// 输出 1}}该方案为什么是“最优”避坑指南优势 1完美的解耦与引用同步代码中字典和队列里存的不是简单的字符串而是DeviceItem对象的内存指针引用。你在任何地方通过字典修改了device.Status Pass队列里那个对应的器件状态会自动跟着变。下料时直接出队拿到的就是最新状态不需要再去字典里二次匹配。优势 2彻底杜绝“中途超车”导致的物理错位在流水线上测试结束的顺序字典更新的顺序可能因为多工位并行而发生乱序比如 2 号比 1 号先测完。但是物料在传送带上的物理顺序是绝对不会变的。下料位直接用Queue.TryDequeue卡死了解析顺序必须和放料顺序一致从而保证了软件数据和硬件物理位置的严格 100% 同步。优势 3防止内存溢出Memory Leak在下料结束时执行了_deviceDict.TryRemove。器件出队下料后它的数据就从字典和队列中彻底销毁了程序运行几个月、几年都不会吃内存。
队列 + 字典的方式 ,放完料 key是器件码,value值是fail,测试结束正好,根据器件去更新value值,到下料位找前面的器件码,获取value值,告知PLC搬运
这种“先投料绑定器件码、中间异步测试、最后当下料位触发时根据测试结果通知PLC搬运”的场景在工业自动化控制中非常经典。使用“队列 字典”的思路完全正确。队列用来保持器件在流水线上的物理先后顺序字典用来实现根据器件码快速检索和更新测试结果。为了确保整个流程在多线程、高并发的工业现场比如扫码、测试完成、下料触发由不同PLC信号或线程驱动下绝对安全且不漏数据以下是为你设计的最优 C# 解决方案。核心设计思路统一的数据实体Data Model将器件码和测试状态封装成一个对象这样队列和字典可以共享同一个对象的引用。线程安全Thread-Safety工业现场的“放料”、“测试”、“下料”通常由不同的事件或线程触发。必须使用ConcurrentDictionary和带锁的队列或ConcurrentQueue防止数据竞争。先进先出FIFO与物理同步下料位触发时直接从队列头部Dequeue出队出来的器件就是物理上第一个到达下料位的器件。此时再去字典里查询它最终的测试状态。最优代码实现 (C#)usingSystem;usingSystem.Collections.Concurrent;usingSystem.Collections.Generic;namespaceIndustrialAutomation{// 1. 定义器件状态枚举publicenumTestResult{Running,// 测试中Pass,// 测试通过Fail// 测试失败默认初始值}// 2. 定义器件实体publicclassDeviceItem{publicstringDeviceCode{get;set;}publicTestResultStatus{get;set;}publicDateTimeTimestamp{get;set;}publicDeviceItem(stringdeviceCode){DeviceCodedeviceCode;StatusTestResult.Fail;// 放料时默认为 FailTimestampDateTime.Now;}}// 3. 核心流水线控制引擎publicclassProductionLineManager{// 线程安全的字典用于根据器件码快速更新状态privatereadonlyConcurrentDictionarystring,DeviceItem_deviceDictnew();// 线程安全的队列用于维持器件在传送带上的物理顺序privatereadonlyConcurrentQueueDeviceItem_deviceQueuenew();/// summary/// 步骤一放料位触发扫码绑定/// /summarypublicvoidOnDeviceLoaded(stringdeviceCode){if(string.IsNullOrWhiteSpace(deviceCode))return;// 创建新器件对象默认 FailvarnewItemnewDeviceItem(deviceCode);// 存入字典如果键已存在则更新正常情况不应重复_deviceDict[deviceCode]newItem;// 存入队列记录物理顺序_deviceQueue.Enqueue(newItem);Console.WriteLine($[放料完成] 器件码:{deviceCode}, 初始状态:{newItem.Status});}/// summary/// 步骤二测试位触发测试结束异步更新结果/// /summarypublicvoidOnTestCompleted(stringdeviceCode,boolisPass){// 根据器件码直接从字典中找出对应的对象引用if(_deviceDict.TryGetValue(deviceCode,outvardevice)){// 修改对象属性队列中的对应对象也会同步变更引用类型device.StatusisPass?TestResult.Pass:TestResult.Fail;Console.WriteLine($[测试完成] 器件码:{deviceCode}, 结果更新为:{device.Status});}else{// 异常处理测试触发了但系统里没有这个码Console.WriteLine($[错误] 收到未注册的器件测试结果:{deviceCode});}}/// summary/// 步骤三下料位触发PLC感应到器件到达下料点/// /summary/// returns返回 0: Fail/异常搬运, 1: Pass搬运/returnspublicintOnDeviceAtUnloadStation(){// 1. 从队列头部取出物理上第一个到达的器件if(_deviceQueue.TryDequeue(outvarcurrentDevice)){// 2. 移除字典中的缓存防止内存泄漏_deviceDict.TryRemove(currentDevice.DeviceCode,out_);Console.WriteLine($[下料触发] 检测到器件:{currentDevice.DeviceCode}, 最终状态:{currentDevice.Status});// 3. 根据最终状态返回告知PLC的控制信号// 假设1 代表 Pass 轨道0 代表 Fail 轨道returncurrentDevice.StatusTestResult.Pass?1:0;}// 异常情况PLC下料位感应到了物料但软件队列里没数据可能有人手动拿放料或传感器误报Console.WriteLine([严重错误] 下料位触发但队列中无器件数据);return0;// 安全起见当作 Fail 抛弃或报警}}}流程运作演示我们可以模拟一个标准的流水线运转过程放料 - 开始测试 - 另一个放料 - 测试结束 - 下料classProgram{staticvoidMain(){ProductionLineManagermanagernewProductionLineManager();Console.WriteLine(--- 模拟流水线开始运转 ---\n);// 1. 放入 1 号芯片manager.OnDeviceLoaded(SN_001);// 2. 放入 2 号芯片manager.OnDeviceLoaded(SN_002);// 3. 2号芯片测试速度快先结束了结果为 PASSmanager.OnTestCompleted(SN_002,true);// 4. 1号芯片测试结束结果为 PASSmanager.OnTestCompleted(SN_001,true);Console.WriteLine(\n--- 开始到达下料位 ---);// 5. 物理上传送带先到的必然是 1 号intplcSignal1manager.OnDeviceAtUnloadStation();Console.WriteLine($通知PLC信号 (1号):{plcSignal1});// 输出 1// 6. 接着 2 号到达下料位intplcSignal2manager.OnDeviceAtUnloadStation();Console.WriteLine($通知PLC信号 (2号):{plcSignal2});// 输出 1}}该方案为什么是“最优”避坑指南优势 1完美的解耦与引用同步代码中字典和队列里存的不是简单的字符串而是DeviceItem对象的内存指针引用。你在任何地方通过字典修改了device.Status Pass队列里那个对应的器件状态会自动跟着变。下料时直接出队拿到的就是最新状态不需要再去字典里二次匹配。优势 2彻底杜绝“中途超车”导致的物理错位在流水线上测试结束的顺序字典更新的顺序可能因为多工位并行而发生乱序比如 2 号比 1 号先测完。但是物料在传送带上的物理顺序是绝对不会变的。下料位直接用Queue.TryDequeue卡死了解析顺序必须和放料顺序一致从而保证了软件数据和硬件物理位置的严格 100% 同步。优势 3防止内存溢出Memory Leak在下料结束时执行了_deviceDict.TryRemove。器件出队下料后它的数据就从字典和队列中彻底销毁了程序运行几个月、几年都不会吃内存。