这种“先投料(绑定器件码)、中间异步测试、最后当下料位触发时根据测试结果通知PLC搬运”的场景,在工业自动化控制中非常经典。

使用 “队列 + 字典” 的思路完全正确。队列用来保持器件在流水线上的物理先后顺序,字典用来实现根据器件码快速检索和更新测试结果

为了确保整个流程在多线程、高并发的工业现场(比如扫码、测试完成、下料触发由不同PLC信号或线程驱动)下绝对安全且不漏数据,以下是为你设计的最优 C# 解决方案。


核心设计思路

  1. 统一的数据实体(Data Model):
    将器件码和测试状态封装成一个对象,这样队列和字典可以共享同一个对象的引用。
  2. 线程安全(Thread-Safety):
    工业现场的“放料”、“测试”、“下料”通常由不同的事件或线程触发。必须使用 ConcurrentDictionary 和带锁的队列(或 ConcurrentQueue),防止数据竞争。
  3. 先进先出(FIFO)与物理同步:
    下料位触发时,直接从队列头部 Dequeue(出队)出来的器件,就是物理上第一个到达下料位的器件。此时再去字典里查询它最终的测试状态。

最优代码实现 (C#)

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace IndustrialAutomation
{
    // 1. 定义器件状态枚举
    public enum TestResult
    {
        Running, // 测试中
        Pass,    // 测试通过
        Fail     // 测试失败(默认初始值)
    }

    // 2. 定义器件实体
    public class DeviceItem
    {
        public string DeviceCode { get; set; }
        public TestResult Status { get; set; }
        public DateTime Timestamp { get; set; }

        public DeviceItem(string deviceCode)
        {
            DeviceCode = deviceCode;
            Status = TestResult.Fail; // 放料时默认为 Fail
            Timestamp = DateTime.Now;
        }
    }

    // 3. 核心流水线控制引擎
    public class ProductionLineManager
    {
        // 线程安全的字典:用于根据器件码快速更新状态
        private readonly ConcurrentDictionary<string, DeviceItem> _deviceDict = new();
        
        // 线程安全的队列:用于维持器件在传送带上的物理顺序
        private readonly ConcurrentQueue<DeviceItem> _deviceQueue = new();

        /// <summary>
        /// 步骤一:放料位触发(扫码绑定)
        /// </summary>
        public void OnDeviceLoaded(string deviceCode)
        {
            if (string.IsNullOrWhiteSpace(deviceCode)) return;

            // 创建新器件对象,默认 Fail
            var newItem = new DeviceItem(deviceCode);

            // 存入字典(如果键已存在则更新,正常情况不应重复)
            _deviceDict[deviceCode] = newItem;
            
            // 存入队列,记录物理顺序
            _deviceQueue.Enqueue(newItem);

            Console.WriteLine($"[放料完成] 器件码: {deviceCode}, 初始状态: {newItem.Status}");
        }

        /// <summary>
        /// 步骤二:测试位触发(测试结束,异步更新结果)
        /// </summary>
        public void OnTestCompleted(string deviceCode, bool isPass)
        {
            // 根据器件码直接从字典中找出对应的对象引用
            if (_deviceDict.TryGetValue(deviceCode, out var device))
            {
                // 修改对象属性,队列中的对应对象也会同步变更(引用类型)
                device.Status = isPass ? TestResult.Pass : TestResult.Fail;
                Console.WriteLine($"[测试完成] 器件码: {deviceCode}, 结果更新为: {device.Status}");
            }
            else
            {
                // 异常处理:测试触发了,但系统里没有这个码
                Console.WriteLine($"[错误] 收到未注册的器件测试结果: {deviceCode}");
            }
        }

        /// <summary>
        /// 步骤三:下料位触发(PLC感应到器件到达下料点)
        /// </summary>
        /// <returns>返回 0: Fail/异常搬运, 1: Pass搬运</returns>
        public int OnDeviceAtUnloadStation()
        {
            // 1. 从队列头部取出物理上第一个到达的器件
            if (_deviceQueue.TryDequeue(out var currentDevice))
            {
                // 2. 移除字典中的缓存,防止内存泄漏
                _deviceDict.TryRemove(currentDevice.DeviceCode, out _);

                Console.WriteLine($"[下料触发] 检测到器件: {currentDevice.DeviceCode}, 最终状态: {currentDevice.Status}");

                // 3. 根据最终状态,返回告知PLC的控制信号
                // 假设:1 代表 Pass 轨道,0 代表 Fail 轨道
                return currentDevice.Status == TestResult.Pass ? 1 : 0;
            }

            // 异常情况:PLC下料位感应到了物料,但软件队列里没数据(可能有人手动拿放料或传感器误报)
            Console.WriteLine("[严重错误] 下料位触发,但队列中无器件数据!");
            return 0; // 安全起见,当作 Fail 抛弃或报警
        }
    }
}


流程运作演示

我们可以模拟一个标准的流水线运转过程(放料 -> 开始测试 -> 另一个放料 -> 测试结束 -> 下料):

class Program
{
    static void Main()
    {
        ProductionLineManager manager = new ProductionLineManager();

        Console.WriteLine("--- 模拟流水线开始运转 ---\n");

        // 1. 放入 1 号芯片
        manager.OnDeviceLoaded("SN_001");

        // 2. 放入 2 号芯片
        manager.OnDeviceLoaded("SN_002");

        // 3. 2号芯片测试速度快,先结束了,结果为 PASS
        manager.OnTestCompleted("SN_002", true);

        // 4. 1号芯片测试结束,结果为 PASS
        manager.OnTestCompleted("SN_001", true);

        Console.WriteLine("\n--- 开始到达下料位 ---");

        // 5. 物理上传送带先到的必然是 1 号
        int plcSignal1 = manager.OnDeviceAtUnloadStation();
        Console.WriteLine($"通知PLC信号 (1号): {plcSignal1}"); // 输出 1

        // 6. 接着 2 号到达下料位
        int plcSignal2 = manager.OnDeviceAtUnloadStation();
        Console.WriteLine($"通知PLC信号 (2号): {plcSignal2}"); // 输出 1
    }
}


该方案为什么是“最优”?(避坑指南)

优势 1:完美的解耦与引用同步
代码中字典和队列里存的不是简单的字符串,而是 DeviceItem 对象的内存指针(引用)。你在任何地方通过字典修改了 device.Status = Pass,队列里那个对应的器件状态会自动跟着变。下料时直接出队,拿到的就是最新状态,不需要再去字典里二次匹配。
优势 2:彻底杜绝“中途超车”导致的物理错位
在流水线上,测试结束的顺序(字典更新的顺序)可能因为多工位并行而发生乱序(比如 2 号比 1 号先测完)。但是,物料在传送带上的物理顺序是绝对不会变的
下料位直接用 Queue.TryDequeue,卡死了解析顺序必须和放料顺序一致,从而保证了软件数据和硬件物理位置的严格 100% 同步。
优势 3:防止内存溢出(Memory Leak)
在下料结束时,执行了 _deviceDict.TryRemove。器件出队下料后,它的数据就从字典和队列中彻底销毁了,程序运行几个月、几年都不会吃内存。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐