.NET 6 WebAPI仅3个PostEvictionCallbacks触发异常求助
问题分析
你的问题核心在于每次调用SetCache<T>时都创建了新的PhysicalFileProvider局部实例,且没有保留对它的引用。当.NET垃圾回收器(GC)回收这个局部的PhysicalFileProvider后,它对应的文件变更监听会失效,导致关联的缓存项无法收到文件变更通知,自然不会触发PostEvictionCallback。
从日志可见,只有Wind、WeatherInfo、DataSlice三个键的回调被触发,这大概率是因为这三个对应的PhysicalFileProvider实例暂时未被GC回收,而另外三个(Rain、Humi、Temp)的实例已被回收,失去了监听能力。
解决方案
需要确保PhysicalFileProvider的生命周期与应用一致,避免被GC回收。推荐通过依赖注入注册单例的方式实现,符合.NET设计规范。
修改后的代码示例
1. 注册PhysicalFileProvider为单例(Program.cs)
在Program.cs的服务注册段添加:
builder.Services.AddSingleton<IFileProvider>(sp => new PhysicalFileProvider(Service.DataFolderPath));
2. 修改FileService,注入并复用IFileProvider
private readonly IFileProvider _fileProvider; private readonly IMemoryCache _cache; private readonly ILogger<FileService> _logger; // 构造函数注入IFileProvider public FileService(IMemoryCache cache, ILogger<FileService> logger, IFileProvider fileProvider) { _cache = cache; _logger = logger; _fileProvider = fileProvider; } public void SetCache<T>(string cacheKey, IEnumerable<T> list) { _logger.LogInformation("========= set cachekey: " + cacheKey); // 复用注入的FileProvider创建变更令牌 IChangeToken changeToken = _fileProvider.Watch(Service.DataFileName); MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() .AddExpirationToken(changeToken) .RegisterPostEvictionCallback(CacheExpireHandler); _cache.Set(cacheKey, list, cacheEntryOptions); if (cacheKey == nameof(DataSlice)) { PreviousDataSlice = (IEnumerable<DataSlice>)list; } }
3. 可选优化:共享同一个变更令牌
如果所有缓存项都监听同一个文件,可复用同一个IChangeToken实例减少资源消耗:
private IChangeToken _fileChangeToken; public FileService(IMemoryCache cache, ILogger<FileService> logger, IFileProvider fileProvider) { _cache = cache; _logger = logger; _fileProvider = fileProvider; // 初始化变更令牌,变更后自动重新监听 RefreshChangeToken(); } private void RefreshChangeToken() { _fileChangeToken = _fileProvider.Watch(Service.DataFileName); _fileChangeToken.RegisterChangeCallback(_ => RefreshChangeToken(), null); } public void SetCache<T>(string cacheKey, IEnumerable<T> list) { _logger.LogInformation("========= set cachekey: " + cacheKey); MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() .AddExpirationToken(_fileChangeToken) .RegisterPostEvictionCallback(CacheExpireHandler); _cache.Set(cacheKey, list, cacheEntryOptions); if (cacheKey == nameof(DataSlice)) { PreviousDataSlice = (IEnumerable<DataSlice>)list; } }
验证逻辑
修改后,所有缓存项都会使用生命周期稳定的PhysicalFileProvider实例创建变更令牌,GC不会回收它。修改SQLite文件后,查看日志即可确认六个缓存键的回调是否全部被触发。
内容的提问来源于stack exchange,提问作者Ching Wang
相关产品推荐
相关产品推荐

