按照SMTUpperComputer Program的依赖注入风格(`Microsoft.Extensions.DependencyInjection` + 工厂 lambda + `ServiceP
优化方案总结按照SMTUpperComputer Program的依赖注入风格Microsoft.Extensions.DependencyInjection 工厂 lambda ServiceProvider在WPF Prism项目中我们采用Prism IContainerRegistry默认 Unity来实现完全一致的 DI 模式。核心思路把AuxPLC改造成纯 DI 友好的服务构造函数注入 实现抽象方法。去掉HardwareMgr里的new AuxPLC()硬编码改为通过 Prism 容器工厂创建。HardwareManager原HardwareMgr重构成为可注入的单例服务统一管理所有站点的硬件服务实例。WPF UIViewModel通过构造函数注入直接使用IAuxPLC或HardwareManager完全符合 Prism MVVM 最佳实践。1. 优化后的 AuxPLC核心改动usinglog4net;usingMaxWell.Interface;usingMaxWell.Model;usingSystem.Threading.Tasks;namespaceMaxWell.Service{/// summary/// 优化后的 AuxPLC - 完全基于 HardwareServiceBase 构造函数 DI/// /summarypublicclassAuxPLC:HardwareServiceBase,IAuxPLC{// 保留业务需要的字段把原来散乱的字段迁移到这里删除与基类重复的privatereadonlyILog_legacyLogLogManager.GetLogger(ROBOT);// 兼容旧日志privatebool_errorProcessing;privatestring_lastErrorCode;// ... 其他你原来 AuxPLC 里的业务字段m_StartTicks、SetV、ManualControl 等publicstringSectionId{get;set;}publicboolErrorProcessing{get_errorProcessing;set_errorProcessingvalue;}publicstringErrCode{get_lastErrorCode;privateset_lastErrorCodevalue;}/// summary/// Prism DI 构造函数完全参照 SMTUpperComputer 的工厂方式/// /summarypublicAuxPLC(IHardwareDriverdriver,IStationRepositoryrepository,IAlarmQueuealarmQueue,stringstationId,stringserviceName):base(driver,repository,alarmQueue,stationId,serviceName){// 基类已注入 _logger、_driver 等SectionIdstationId;_logger.Info($[{stationId}] AuxPLC 实例创建完成);}/// summary/// 【必须实现】返回当前站点的硬件配置从 Stations.json 或 appsettings.json 读取/// /summaryprotectedoverrideHardwareConfigGetHardwareConfig(){// 示例你可以注入 IConfiguration 后再读取也可从 TestSection 模型里取// 这里直接使用基类已有的 IpAddress / Port / Driver 等LoadConfig 时已填充returnnewHardwareConfig{DriverTypeDriverType,IpAddressIpAddress,PortPort,MaxThreads4,BufferSize1024// ... 其他硬件加速/PLC 配置};}/// summary/// 【必须实现】核心业务数据处理原来 AuxPLC 里所有的逻辑迁移到这里/// /summaryprotectedoverrideasyncTaskProcessHardwareDataAsync(HardwareDatadata){// 这里放原来 AuxPLC 的业务逻辑状态机、错误处理、RateQuery、GatePolarity 等// 示例_logger.Info($[{StationId}] AuxPLC 收到数据:{data.Address}{data.Value});// 原来你写的各种处理...if(data.AddressPLC.Status){// 处理 PLC 状态}awaitTask.CompletedTask;// 异步业务}// 你原来的其他公开方法StartAsync、ExecuteAsync 等已由基类提供无需重写}}关键改动删除空构造函数改为带参数的 DI 构造函数。把原来throw new NotImplementedException()的两个抽象方法真正实现。所有日志、驱动、心跳、Channel 消费全部走基类HardwareServiceBase代码更干净。2. Prism 中注册服务完全模仿 SMTUpperComputer在App.xaml.cs的RegisterTypes方法里注册与 SMT 的services.AddSingleton完全对应protectedoverridevoidRegisterTypes(IContainerRegistrycontainerRegistry){// 1. 配置完全同 SMTUpperComputervarconfigurationnewConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(appsettings.json,optional:false,reloadOnChange:true).Build();containerRegistry.RegisterInstanceIConfiguration(configuration);// 2. 公共服务containerRegistry.RegisterSingletonILogger,YourLogger();// 替换成你的日志实现containerRegistry.RegisterSingletonIHardwareDriver,YourPlcDriver();// PLC 驱动containerRegistry.RegisterSingletonIStationRepository,StationRepository();containerRegistry.RegisterSingletonIAlarmQueue,AlarmQueue();// 3. 硬件服务工厂关键支持多站点containerRegistry.RegisterSingletonIHardwareServiceFactory,HardwareServiceFactory();// 4. HardwareManager取代原来的静态 HardwareMgrcontainerRegistry.RegisterSingletonHardwareManager();}3. 新增 HardwareServiceFactory支持多站点publicinterfaceIHardwareServiceFactory{IAuxPLCCreateAuxPLC(stringstationId);// 可以继续加 CreateMainBoard、CreateRFID 等}publicclassHardwareServiceFactory:IHardwareServiceFactory{privatereadonlyIContainerProvider_container;publicHardwareServiceFactory(IContainerProvidercontainer){_containercontainer;}publicIAuxPLCCreateAuxPLC(stringstationId){// 完全模仿 SMTUpperComputer 的 lambda 工厂方式return_container.ResolveAuxPLC(newParameterOverride(stationId,stationId),newParameterOverride(serviceName,AuxPLC));}}4. 重构 HardwareManager取代 HardwareMgrpublicclassHardwareManager{privatereadonlyIHardwareServiceFactory_factory;publicDictionarystring,TestSectionTestAreaMap{get;privateset;}new();publicHardwareManager(IHardwareServiceFactoryfactory){_factoryfactory;}publicvoidLoadConfig(){/* 原来 HardwareMgr.LoadConfig() 的全部代码 */}publicasyncTaskInitializeHardWareAsync(){foreach(vartestinTestAreaMap.Values){// 使用工厂创建真正的 DI 服务实例test.AuxPLC_factory.CreateAuxPLC(test.Id);// 注意TestSection 的 AuxPLC 属性改成 IAuxPLC 类型awaittest.AuxPLC.InitializeAsync();awaittest.AuxPLC.StartAsync();// 其他设备同理...}}}TestSection 小改动把AuxPLC属性类型从旧的AuxPLC改成接口IAuxPLC推荐。5. WPF UIViewModel如何使用服务示例MainViewModelpublicclassMainViewModel:BindableBase{privatereadonlyHardwareManager_hardwareManager;privatereadonlyIAuxPLC_auxPlc;// 如果只需要某个固定站点的服务// Prism 会自动注入publicMainViewModel(HardwareManagerhardwareManager,IAuxPLCauxPlcnull){_hardwareManagerhardwareManager;_auxPlcauxPlc;// 如果注册了默认站点也可以直接注入}publicasyncTaskStartAllHardware(){await_hardwareManager.InitializeHardWareAsync();}publicasyncTaskControlAuxPLC(stringstationId,boolopenGate){varaux_hardwareManager.TestAreaMap[stationId].AuxPLC;// 或通过工厂获取awaitaux.ExecuteAsync(newSignalObject{AddressGate.Open,ValueopenGate});}}XAML 使用Prism 自动解析 ViewModelWindow ... xmlns:prismhttp://prismlibrary.com/ prism:ViewModelLocator.AutoWireViewModelTruePrism 自动注入规则只要 ViewModel 的构造函数参数在容器里注册过Prism 就会自动注入无需[ImportingConstructor]Unity 默认支持。6. 启动流程OnStartupprotectedoverrideasyncvoidOnStartup(StartupEventArgse){boolret;mutexnewMutex(true,this.GetType().FullName,outret);if(!ret)Environment.Exit(0);base.OnStartup(e);// 1. 解析 Manager已完成所有 DIvarhardwareManagerContainer.ResolveHardwareManager();hardwareManager.LoadConfig();// 加载 Stations.jsonawaithardwareManager.InitializeHardWareAsync();// 启动所有硬件服务}总结对比SMTUpperComputer vs 本方案项目SMTUpperComputer (WinForms)本方案 (WPF Prism)DI 容器ServiceCollectionBuildServiceProviderIContainerRegistryUnity服务注册方式services.AddSingleton lambda 工厂RegisterSingletonIHardwareServiceFactory多站点支持手动循环创建工厂 stationId 参数UI 使用方式MainForm构造函数注入ViewModel 构造函数注入Prism 自动配置加载Program.MainRegisterTypesHardwareManager这样改造后你的代码结构和SMTUpperComputer完全一致同时完美适配 Prism MVVM WPF完全可测试、可替换、可扩展。需要我继续提供IHardwareDriver、TestSection完整模型修改、或具体某个业务逻辑迁移示例随时告诉我HardwareServiceBase 深入实现细节讲解HardwareServiceBase是整个硬件服务层Service 层的核心抽象基类它严格遵循了“驱动与业务彻底分离100% 异步支持多站点”的设计原则。下面我将逐层、逐方法、逐字段进行深入剖析结合代码逻辑、设计意图、生命周期、线程/异步安全、异常处理等多个维度进行详细讲解。1. 整体设计目标与架构定位职责统一管理硬件驱动_driver的生命周期初始化、心跳、Channel 消费、停止、释放。提供业务层统一入口Execute/Read/Write 等快捷方法让上层业务代码完全不需要关心底层驱动细节。通过Channel实现生产者-消费者解耦驱动只负责把原始硬件数据推入 Channel业务层在独立 Task 中异步消费处理。实现优雅启动/停止和资源自动释放支持IAsyncDisposable。与 SMTUpperComputer 的对应它相当于 SMT 项目中ICommunicationService、IVisionProcessingService等服务的通用模板只是把“硬件”这一层抽象得更彻底。关键设计模式模板方法模式StartAsync、ConsumeChannelAsync是模板ProcessHardwareDataAsync、GetHardwareConfig是抽象钩子。依赖倒置依赖抽象IHardwareDriver、IStationRepository等。异步流ChannelReader.ReadAllAsyncawait foreach。2. 核心受保护字段protected 字段protectedreadonlyIHardwareDriver_driver;// 驱动实例必须注入protectedreadonlyIStationRepository_repository;// 站点仓库可读写数据库配置protectedreadonlyIAlarmQueue_alarmQueue;// 告警队列统一上报异常protectedreadonlyILog_logger;// log4net 日志按 ServiceName 隔离protectedCancellationTokenSource?_cts;// 全局取消令牌源protectedTask?_consumerTask;// Channel 消费后台 Taskprotectedbool_isRunning;// 服务是否正在运行线程可见privatebool_disposed;// 防止重复释放注意这些字段全部是protected readonly或private子类可以直接访问但不能随意替换_driver。_cts是CreateLinkedTokenSource可以同时接受外部传入的全局取消令牌实现跨服务统一关闭。3. 公共属性IHardwareService 接口 兼容老代码接口属性必须实现StationId、ServiceName来自 Stations.json每个站点服务唯一标识。IsRunning只读外部监控用。FlowStatus业务流程状态枚举可扩展。兼容老代码的 m_ 开头属性历史遗留protectedstringm_Id,m_Name,m_IpAddress,m_Port,m_DriverType...publicstringIpAddress{getm_IpAddress;setm_IpAddressvalue;}这些属性主要为了兼容原来 HardwareMgr.LoadConfig()中直接给testSection.AuxPLC.IpAddress xxx赋值而保留。推荐做法未来逐步迁移到GetHardwareConfig()中读取避免属性和配置双份维护。4. 构造函数当前代码中被注释的状态//protected HardwareServiceBase(// IHardwareDriver driver,// IStationRepository repository,// IAlarmQueue alarmQueue,// string stationId,// string serviceName)//{// _driver driver ?? throw new ArgumentNullException(nameof(driver));// ...// StationId stationId;// ServiceName serviceName;// _logger LogManager.GetLogger($Service.{serviceName});//}当前问题因为构造函数被注释AuxPLC只能使用空构造函数导致_driver、StationId等字段永远是null/ 默认值这也是你原来AuxPLC里两个方法直接throw NotImplementedException的根本原因。正确做法强烈建议立即取消注释并使用在HardwareServiceFactory中通过 Prism 的ParameterOverride注入。子类AuxPLC必须调用base(driver, repository, alarmQueue, stationId, serviceName)。5. 初始化与卸载流程InitializeAsync / UninitializeAsyncInitializeAsync推荐在HardwareManager中调用publicvirtualasyncTaskInitializeAsync(CancellationTokencancellationTokendefault){if(_disposed)thrownewObjectDisposedException(ServiceName);_logger.Info($[{StationId}]{ServiceName}开始初始化...);try{varconfigGetHardwareConfig();// ← 子类必须实现await_driver.InitializeAsync(config,cancellationToken);// 驱动初始化_logger.Info($[{StationId}]{ServiceName}初始化完成);}catch(Exceptionex){_logger.Error($[{StationId}]{ServiceName}初始化失败,ex);throw;// 向上抛出让 HardwareManager 捕获并决定是否继续启动其他站}}UninitializeAsync先调用StopAsync确保停止运行。如果驱动实现了IAsyncDisposable则异步释放驱动资源。异常只记录日志不向上抛出防止卸载阶段把整个程序搞崩。6. 启动与停止机制StartAsync / StopAsync—— 最核心部分StartAsync检查_driver.Enable配置文件中可禁用某个硬件。创建_cts联动外部取消令牌。await _driver.StartAsync(_cts.Token)→ 启动驱动心跳线程3-5秒一次超时800ms3次失败自动重连。启动_consumerTask Task.Run(() ConsumeChannelAsync(), _cts.Token)→独立后台 Task消费 Channel。_isRunning true。StopAsync_isRunning false_cts?.Cancel()await Task.WhenAny(_consumerTask, Task.Delay(8000))→最多等待 8 秒防止卡死日志记录“已停止”。异步安全所有状态变更都在Start/Stop中完成ConsumeChannelAsync使用_cts.Token自动退出。7. Channel 数据消费核心ConsumeChannelAsync这是整个基类的灵魂protectedvirtualasyncTaskConsumeChannelAsync(){try{awaitforeach(vardatain_driver.DataChannelReader.ReadAllAsync(_cts!.Token)){awaitProcessHardwareDataAsync(data);// ← 子类实现具体业务}}catch(OperationCanceledException){/* 正常停止不算错误 */}catch(Exceptionex){_logger.Error($[{StationId}]{ServiceName}Channel 消费异常,ex);_alarmQueue.Enqueue(newAlarmItem(...));// 统一告警}}优点使用ReadAllAsyncawait foreach→零拷贝、高性能的异步流。驱动只需writer.TryWrite(data)即可业务层完全解耦。异常统一进入告警队列不影响其他站点。8. 业务快捷操作方法Execute/Read/WritepublicvirtualasyncTaskHardwareResultExecuteAsync(SignalObjectinstruction,CancellationTokenctdefault){instructioninstructionwith{StationIdStationId};// 自动补全站点IDreturnawait_driver.WriteAsync(instruction,ct);}publicvirtualTaskHardwareResultReadAsync(...)// 同理publicvirtualTaskHardwareResultWriteAsync(...)// 同理SignalObject是统一指令模型Address、Value、DataType、StationId 等。with表达式C# 10自动拷贝并覆盖 StationId保证指令永远属于当前服务。所有调用最终都落到_driver上业务层不需要知道驱动是 Modbus、TCP、OPC UA 还是别的。9. 资源释放DisposeAsync实现了IAsyncDisposablepublicasyncValueTaskDisposeAsync(){if(_disposed)return;_disposedtrue;awaitUninitializeAsync();// 自动停止 释放驱动}在HardwareManager中可以await using var service ...或在程序退出时统一Dispose。10. 子类必须实现的两个抽象方法protected abstract HardwareConfig GetHardwareConfig()→ 返回驱动需要的配置IP、Port、DriverType、超时等。推荐从Stations.json或IConfiguration中读取。protected abstract Task ProcessHardwareDataAsync(HardwareData data)→ 所有业务逻辑状态机、错误处理、RateQuery、GatePolarity、SetV 等全部写在这里。11. 总结生命周期全景图HardwareManager ↓ Create AuxPLC (DI 构造函数注入) ↓ InitializeAsync() → GetHardwareConfig() → _driver.InitializeAsync() ↓ StartAsync() → _driver.StartAsync(心跳) ConsumeChannelAsync(Task) ↓ 数据到达 → _driver 推 Channel → await foreach → ProcessHardwareDataAsync(业务) ↓ StopAsync() / UninitializeAsync() / DisposeAsync()当前代码痛点 优化建议已在上次回答中给出构造函数必须取消注释 使用HardwareServiceFactory。把AuxPLC里散乱的字段迁移到protected字段或专用业务属性中。HardwareMgr改为HardwareManager单例 异步初始化。如果你需要我把完整修正后的HardwareServiceBaseAuxPLCHardwareServiceFactory代码一次性贴出来或者针对某个具体方法如ConsumeChannelAsync的异常重试机制再深入展开随时告诉我