应用闲置数小时后使用ServiceLocator获取IXXXXXController触发激活错误求助
这问题我在维护Prism MEF项目时碰到过,核心和MEF的弱引用缓存机制以及闲置时的GC回收行为有关,咱们一步步拆解:
原因剖析
从错误堆栈里的WeakReferenceCollection.CleanupDeadReferences就能看出端倪:MEF默认对共享(Shared)部件是用弱引用存储的。当应用长时间闲置时,系统会触发垃圾回收(GC),如果你的IXXXXXController实例没有被其他强引用持有,就会被GC回收。后续再通过ServiceLocator获取时,MEF找不到已缓存的实例,尝试重新激活又因为依赖链上的部件可能也被回收,最终抛出激活错误。
你的配置里用AssemblyCatalog添加了程序集,但MEF的默认共享策略就是弱引用追踪,这才导致闲置后出问题。
解决方案
根据你的需求(是否需要单例),可以选下面的方案:
方案1:改为非共享实例(适合不需要单例的场景)
给IXXXXXController的实现类添加PartCreationPolicy特性,指定每次获取都创建新实例,彻底避开弱引用缓存的问题:
[Export(typeof(IXXXXXController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class XXXXXController : IXXXXXController { // 类实现代码 }
这样每次调用ServiceLocator.Current.GetInstance<IXXXXXController>()都会生成新实例,不会依赖MEF的弱引用缓存,自然不会被GC回收导致激活失败。
方案2:保持单例,添加强引用持有(适合需要单例的场景)
如果你的控制器必须是单例,那就在应用启动时提前获取实例,并用一个静态变量保持强引用,阻止GC回收:
public class YourBootstrapper : MefBootstrapper { // 静态变量持有强引用,防止GC回收实例 private static IXXXXXController _cachedController; protected override void InitializeShell() { // 提前获取实例并缓存 _cachedController = ServiceLocator.Current.GetInstance<IXXXXXController>(); base.InitializeShell(); } protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ClassName).Assembly)); } }
这个静态变量会一直持有实例的强引用,GC不会回收它,后续再获取时MEF就能找到缓存的实例。
方案3:尽量避免直接使用ServiceLocator(推荐最佳实践)
ServiceLocator其实是反模式,建议改用构造函数注入的方式获取依赖。比如在需要使用IXXXXXController的类里,直接把它作为构造参数:
[Export] public class SomeService { private readonly IXXXXXController _controller; // 通过构造函数注入,MEF会自动管理生命周期 [ImportingConstructor] public SomeService(IXXXXXController controller) { _controller = controller; } }
这种方式MEF会更可靠地追踪实例的引用关系,减少因为间接获取导致的生命周期问题。
额外检查点
- 确认
IXXXXXController的实现类确实标注了[Export(typeof(IXXXXXController))]特性,没有写错导出类型。 - 确认
ConfigureAggregateCatalog里添加的程序集确实包含该控制器的实现类(typeof(ClassName).Assembly里的ClassName要和控制器在同一个程序集)。
内容的提问来源于stack exchange,提问作者Dev

