MemoryCache初始加载条目与控制器注入结果不一致问题
问题根因
你的判断完全正确,问题核心就是services.BuildServiceProvider()这行代码导致的。services.AddMemoryCache()注册的IMemoryCache默认确实是单例生命周期,但手动调用BuildServiceProvider()会生成一个和应用正式运行所用的根依赖注入容器完全独立的新容器,两个容器持有完全独立的单例对象池:
- 你调用
LoadAll()写入的280条配置,存在手动创建的临时容器持有的MemoryCache实例中 - 控制器注入的IMemoryCache,是应用正式根容器创建的单例对象,和你写入缓存用的MemoryCache根本不是同一个实例
- 临时创建的ServiceProvider没有被持续引用,启动流程执行完成后就会被GC回收,里面存储的缓存条目会被一并销毁。你在控制器拿到的缓存里剩下的16条无关条目,是ASP.NET Core框架和其他组件向正式容器的MemoryCache写入的运行时数据,和你初始加载的配置没有关系。
另外你当前的代码还有两个隐藏问题:
LoadAll()是异步签名的方法,但你在启动时调用没有等待执行完成,存在加载逻辑还没跑完就开始接收请求的风险- 你给缓存条目只设置了滑动过期时间,没有设置缓存优先级,当服务器内存紧张时,MemoryCache会按优先级自动回收低优先级条目,可能导致配置项被提前清除。
正确实现步骤
- 移除ConfigureServices中手动BuildServiceProvider加载缓存的代码,ConfigureServices只保留服务注册逻辑:
public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); // 保留你原有的其他服务注册:ISysParamProvider、DbContext、控制器相关注册都不需要改动 services.AddControllers(); }
- 把缓存预热逻辑移动到
Configure方法中执行,Configure支持直接通过方法参数注入正式容器的服务实例,拿到的就是和控制器注入一致的单例对象:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISysParamProvider<IDbContext> sysParamProvider) { // 保留你原有的中间件配置逻辑,比如异常处理、路由、授权等 // 同步等待缓存加载完成,确保应用启动接收请求前所有配置已经写入缓存 sysParamProvider.LoadAll().GetAwaiter().GetResult(); }
如果是.NET 6及以上版本的最小API项目,直接在var app = builder.Build();之后、app.Run();之前执行加载逻辑即可,支持直接await:
var app = builder.Build(); // 中间件配置... await app.Services.GetRequiredService<ISysParamProvider<IDbContext>>().LoadAll(); app.Run();
- 优化LoadAll方法的实现,修正异步逻辑和缓存配置:
public async Task LoadAll() { // 使用EF Core异步查询,避免阻塞线程 var sysParams = await this.db.Set<SysParam>().ToListAsync(); foreach (var sysParam in sysParams) { if (!this.cache.TryGetValue(sysParam.ParamName, out _)) { var cacheentryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromDays(1)) // 设置高优先级,避免内存紧张时配置项被框架自动回收 .SetPriority(CacheItemPriority.NeverRemove); this.cache.Set(sysParam.ParamName, sysParam.ParamValue, cacheentryOptions); } } }
额外注意事项
- 非测试场景不要在ConfigureServices中手动调用
BuildServiceProvider(),这种写法不仅会导致多实例问题,生成的临时容器无法被框架自动释放,还会造成内存泄漏 - 如果系统参数属于启动后不会变更的固定配置,也可以直接在启动时加载后注册为单例选项,不需要每次请求都读取缓存,性能更高
内容的提问来源于stack exchange,提问作者xeraphim
相关产品推荐
相关产品推荐

