首页 / 资讯中心 / 文章详情

针对 `EQPBizHelper` 类的并发分析及优化意见

针对 `EQPBizHelper` 类的并发分析及优化意见 ★ FEATURED ARTICLE
针对EQPBizHelper类的并发分析及优化意见用户提供了EQPBizHelper类的完整实现该类代表一个测试工站与 PLC 控制器、上位机 UI、MES/EAP 等模块交互。在多工站、多硬件环境下该类的多个方法会被不同线程调用如硬件消息接收线程、定时模拟线程、外部业务驱动线程。不加锁是绝对不可行的当前代码中存在多处严重的数据竞争隐患可能导致程序崩溃、数据错乱或死锁。一、共享资源与并发风险点1.SN2Duts(Dictionarystring, DUT)privateDictionarystring,DUTSN2Duts写入SOT方法中SN2Duts[dutsn] dut;读取/删除EOT方法中SN2Duts.ContainsKey(dutSn)、SN2Duts.Remove(dutSn)并发场景多个托盘同时在不同工站完成测试可能同时调用EOT或者一个工件正在SOT添加时另一个工件EOT尝试删除同一个键虽然不同 SN 一般不会冲突但 Dictionary 内部结构在多线程同时 Add/Remove 时会被破坏引发ArgumentException或IndexOutOfRangeException。风险高并发下程序崩溃。2.WaitProducingDUTs(Queue) 和ProducedDUTs(Queue)写入SOT中不直接操作但模拟线程SimlateProductionLoop中DequeueEOT中ProducedDUTs.Enqueue(dut)并发SimlateProductionLoop线程与EOT同时操作ProducedDUTsWaitProducingDUTs在模拟线程中Dequeue但外部没有Enqueue可能在其他地方仍存在多线程读写。风险Queue非线程安全并发Enqueue/Dequeue导致数据损坏或异常。3.EOTmapQueue(QueueDictionarystring,object)privateQueueDictionarystring,objectEOTmapQueuenewQueueDictionarystring,object();写入MessageReceived中EOTmapQueue.Enqueue(map)读取MessageReceived中EQPEOUSend分支EOTmapQueue.Dequeue()并发两个事件都发生在硬件消息接收线程中但可能先后触发由于Queue非线程安全在高频消息下可能导致枚举失败或计数错误。更危险的是EOT方法也可能通过其他路径调用如直接调用但当前代码中EOT并不直接操作该队列。4.CurrentUnloadSNMap(Dictionarystring, bool)写入EOT中CurrentUnloadSNMap[dutSn] false读取UnloadSNCheck中CurrentUnloadSNMap.ContainsKey(sn)并发EOT可能在多线程中同时执行多个工件完成测试时同时向该字典添加条目造成内部状态损坏。5.NowDoingCnt(int)读写SOT中NowDoingCntEOT中NowDoingCnt--BeforeSOT中读取NowDoingCnt判断并发无锁、无volatile导致自增/自减操作不是原子的可见性无法保证。可能产生计数错误如同时两次只增加一次。6.IsProcessing/HasCacheSpace(bool)写入SOT和EOT中根据NowDoingCnt设置这两个标志读取AutoBizDriver中的IsAllowProduceByCurrentDevice会读取这些标志并发与NowDoingCnt类似缺少同步可能读到过期值导致上料决策错误。7.m_BeforeSotDoing(bool)写入BeforeSOT中先置true最后置false读取BeforeSOT开头检查if (m_BeforeSotDoing) return -1;并发虽意在防止重入但无内存屏障两个线程可能同时看到false并同时进入导致重复执行。8.SN2Site(ConcurrentDictionary)已使用ConcurrentDictionary线程安全但注意Clear()在EOT末尾调用可能与其他线程的TryAdd同时进行风险较小但仍需注意逻辑一致性。二、其他隐式并发问题1.模拟线程SimlateProductionLoop与真实硬件线程的混用当SimulateMode true时该线程会访问WaitProducingDUTs、ProducingDUTs、SN2Site同时SOT、EOT也会被调用模拟模式下也会走SOT、EOT逻辑。两者之间没有同步极易冲突。2.事件回调中的跨线程 UI 操作OnLog、OnException等事件可能在任意线程触发而订阅者可能是 UI 线程。当前代码未使用Control.Invoke/BeginInvoke可能导致 UI 异常。3.DataWrite中操作DataStructTemplateTableDataStructTemplateTable是一个DataTable在SOT和EOT中可能被读写SOT中创建EOT中通过DataWrite添加行。DataTable非线程安全多工件并发测试会导致内部状态损坏。三、优化方案1.全面使用线程安全集合现有类型推荐类型说明Dictionarystring, DUTConcurrentDictionarystring, DUT使用TryAdd、TryRemove、ContainsKeyQueueDUTConcurrentQueueDUT使用Enqueue、TryDequeueQueueobjectConcurrentQueueobject同上QueueDictionarystring,objectConcurrentQueueDictionarystring,object同上Dictionarystring, boolConcurrentDictionarystring, bool使用TryAdd、ContainsKey2.原子操作与内存屏障对于简单计数器NowDoingCnt使用Interlocked.Increment/Interlocked.Decrement。对于布尔标志如m_BeforeSotDoing使用Interlocked.Exchange或声明为volatile并结合lock语句确保原子性。建议使用lock保护整个BeforeSOT入口。privatereadonlyobject_beforeSotLocknewobject();publicintBeforeSOT(objectobj){lock(_beforeSotLock){if(m_BeforeSotDoing)return-1;m_BeforeSotDoingtrue;// ...m_BeforeSotDoingfalse;return0;}}3.保护DataStructTemplateTable和DataWrite操作对DataTable的访问添加列、添加行加lock。privatereadonlyobject_dataTableLocknewobject();lock(_dataTableLock){if(!DataStructTemplateTable.Columns.Contains(paraName))DataStructTemplateTable.Columns.Add(paraName);// 添加行等操作}4.消除模拟线程与真实线程的共享冲突若SimulateMode为 true应完全隔离真实硬件调用。推荐在SimulateMode下禁止任何外部调用SOT/EOT通过标志控制所有模拟逻辑仅由SimlateProductionLoop内部完成。5.事件回调中确保 UI 安全在所有OnLog、OnException调用处如果需要在 UI 上显示订阅方应自行处理Invoke。但更好的做法是在EQPBizHelper内部不假设 UI 存在仅通过事件抛出由上层负责线程封送。6.使用CancellationTokenSource替代while(SimulateMode)中的无限循环便于优雅退出。7.减少共享状态增加不可变设计例如EOTmapQueue中存储的Dictionary对象在入队后不应再被修改。当前代码中入队后立即返回没有修改风险但Dequeue后直接传给EOT方法EOT内部会读取字典内容这是安全的。8.对于IsProcessing和HasCacheSpace由于它们依赖于NowDoingCnt可以通过计算属性实时获取而非存储状态publicboolIsProcessingNowDoingCnt0;publicboolHasCacheSpaceNowDoingCnt2;// 根据实际逻辑这样避免了状态不一致。四、典型竞态示例及修正代码原代码有竞态NowDoingCnt;if(NowDoingCnt0)IsProcessingtrue;if(NowDoingCnt2)HasCacheSpacetrue;修正后原子 无状态标志intnewCountInterlocked.Increment(refNowDoingCnt);// 不再单独维护 IsProcessing/HasCacheSpace改用属性实时计算原代码Dictionary 并发SN2Duts[dutsn]dut;// 可能与其他线程的 Remove 冲突修正后if(!_sn2Duts.TryAdd(dutsn,dut)){Log.Warn($Duplicate SN{dutsn}added to SN2Duts);}五、总结不加锁在高并发、多工站环境下必然导致数据损坏和逻辑错误。建议立即实施以下最低限度修改将所有普通Dictionary/Queue替换为ConcurrentDictionary/ConcurrentQueue。对NowDoingCnt使用Interlocked操作。对BeforeSOT的入口加lock防止重入。对DataStructTemplateTable的所有访问加lock。移除IsProcessing/HasCacheSpace字段改用基于NowDoingCnt的计算属性。在模拟模式下禁止外部调用SOT/EOT或者将模拟线程与真实逻辑完全分离。经过上述优化后系统可在多工站并行加工时保持稳定不会因并发导致崩溃。若需要更高性能可进一步设计无锁数据结构但当前场景下使用Concurrent集合已足够。
阅读完成 · 觉得有帮助?
咨询建站