多工站、多硬件工况下不加锁的风险分析该代码涉及多个并发源多个IPLCBizHelper实例可能在不同线程中触发事件如扫码完成、上料完成、下料完成。Task.Run启动的异步任务EAP 交互、清料等与主流程并行执行。StartLot/EndLot中并行启动多个业务对象的初始化/结批。事件回调中可能递归调用如OnCarriarArrive内部调用自身。不加锁必然导致数据竞争和不一致。以下是具体风险点及示例1.ProductionSiteAbleQueue_MaxWell– 队列并发破坏Dictionarystring,QueueProductionSiteProductionSiteAbleQueue_MaxWell写操作StartLot中初始化EnqueueOnCarriarArrive中DequeueOnUnloadDone_MaxWell中EnqueueOnLoadDone_MaxWell中失败时Enqueue。并发场景两个托盘同时到达不同设备触发两个OnCarriarArrive并行执行同时对一个设备队列Dequeue可能抛出InvalidOperationException队列空或导致计数错误。不加锁后果队列数据损坏托盘分配错误导致设备死锁或物料丢失。2.ProducedCarrierSN– Dictionary 多线程写Dictionarystring,intProducedCarrierSN在OnCommonEvent__PLC的扫码处理分支中当TargetDevice currentDeviceNo时执行Add。多个 PLC 设备可能同时完成扫码并行向同一个Dictionary添加不同键未加锁会引发ArgumentException键已存在或内部状态损坏。3.DataRecord与SNtoIndexMap– DataTable 非线程安全OnLoadDone_MaxWell中调用DataRecord.Rows.Add()并更新SNtoIndexMap。EOT中通过DataRecord.Select()查询并修改行数据。多个测试机同时完成测试并发写入DataTable会导致IndexOutOfRangeException或数据混乱。4. 布尔标志位 – 可见性与乱序执行privateboolm_CurrentLoadSignalDonetrue;privateboolm_EndlotDoingfalse;privateboolClearProductionDonetrue;多个线程读写这些标志例如OnCarriarArrive中设置为falseOnLoadDone_MaxWell中设置为true。没有volatile或内存屏障可能导致一个线程的修改对另一个线程不可见从而出现重复上料或死等。5. 集合m_StartLotDoneList/m_EndLotDoneListOnCommonEvent__EQP中AddRun/EndLot中遍历检查Count。并发Add可能破坏List内部数组且Count检查不准确。优化意见一、强制使用线程安全集合或加锁保护1. 队列改用ConcurrentQueueConcurrentDictionaryprivateConcurrentDictionarystring,ConcurrentQueueProductionSiteProductionSiteAbleQueue_MaxWell;TryDequeue原子操作无需显式锁。但需注意ConcurrentQueue是无界的对于有容量限制的缓存位需要额外控制。2.ProducedCarrierSN→ConcurrentDictionarystring, intprivateConcurrentDictionarystring,intProducedCarrierSNnew();使用TryAdd代替Add避免异常。3.DataRecord与SNtoIndexMap加锁保护privatereadonlyobject_dataLocknewobject();lock(_dataLock){DataRecord.Rows.Add(row);SNtoIndexMap[sn]index;}// 同样在 EOT 中 lock 查询和更新或使用ConcurrentBag存储 DUT 快照UI 更新时再转 DataTable。4. 简单状态标志使用Interlocked或volatileprivateint_currentLoadSignalDone1;// 1true, 0falseInterlocked.Exchange(ref_currentLoadSignalDone,0);if(Interlocked.CompareExchange(ref_currentLoadSignalDone,1,0)0)...或者使用volatile bool配合内存屏障更简单但不适用于读-修改-写。5. 列表改用ConcurrentBag或加锁privateConcurrentBagstringm_StartLotDoneListnew();// 或者 private readonly object _lotLock new object();二、避免在事件回调中执行阻塞/耗时操作问题代码示例OnCommonEvent__PLC中直接弹窗Runtime.MainForm.Invoke(newAction((){m_LoadCarriarSNScanNGForm.ShowDialog();// 阻塞等待用户输入}));这会挂起 PLC 事件处理线程导致后续信号无法及时响应可能触发 PLC 超时。优化将弹窗操作放到独立线程Task.Run回调完成立即返回后续处理通过信号机制或再次触发事件完成。三、消除递归调用和潜在死循环OnCarriarArrive结尾调用自身OnCarriarArrive(sender,null);如果队列为空或条件不满足可能形成紧循环消耗 CPU。应改为设置标志或使用定时器延迟重试而非立即递归。IsAllowProduceByCurrentDevice中的goto IsNextDeviceIsAllProcessing逻辑混乱嵌套循环与标签降低可读性。建议重构为循环方法。四、异步化长时间操作OnCarriarArrive中的while等待BeforeSOTwhile(ok!0){// 循环重试每次 Sleep(1000)}阻塞 PLC 回调线程影响整线响应。优化异步轮询 状态机或使用TaskCompletionSource等待设备就绪信号。五、明确线程模型 – 建议使用单一事件处理管道当前各 PLC/EQP 事件直接在回调中执行业务逻辑线程不可控。优化引入一个队列如ConcurrentQueueAction和单线程消费者所有业务操作上料、下料、状态变更投递到此队列顺序执行。可极大简化并发控制。示例如下privateBlockingCollectionAction_workQueuenew();privateTask_workerTask;voidStartWorker()_workerTaskTask.Run((){foreach(varactionin_workQueue.GetConsumingEnumerable())action();});voidEnqueue(Actionaction)_workQueue.Add(action);所有事件回调只做Enqueue(() { 实际逻辑 })。六、其他建议日志增强在关键操作入口打印线程 ID便于排查竞态。用CancellationTokenSource代替while(状态)Thread.Sleep。EAP 回调M_EAPHelper_OnMessageReceived已经异步但修改ConcurrentDictionary没问题不过注意内部Invoke到 UI 线程可能死锁应使用BeginInvoke。参数解构Dictionarystring, Object频繁装箱拆箱可定义强类型参数类。状态机重构ProductionState有多个状态但切换时未检查合法性可引入状态机类。总结不加锁绝对不可以。该代码在真实多工站并发场景下会频繁崩溃或产生逻辑错误。最低限度的修改是将所有共享集合替换为ConcurrentDictionary/ConcurrentQueue。对DataTable、List等非安全集合加lock。将递归调用改为定时轮询或事件驱动。移除事件回调中的阻塞操作。若要长期稳定运行强烈建议重构为单线程事件循环模型彻底消除并发复杂性。