.NET 6中IMemoryCache缓存意外过期:特性还是Bug?
IMemoryCache:特性还是Bug?
我在.NET 6中对IMemoryCache做了基础测试,测试结果如下:
- PersonAsync符合预期:1秒后过期,从FetchPersonAsync获取新值
- PlaceAsync符合预期:5秒后过期,从FetchPlaceAsync获取新值
- MixedAsync不符合预期:设置的是60秒过期,但实际1秒后就过期,从FetchMixedAsync获取新值
MixedAsync的返回值通过调用PersonAsync和PlaceAsync构建,看起来IMemoryCache因为它依赖了PersonAsync的缓存数据,将其过期时间判定为1秒。
这其实是IMemoryCache(Microsoft.Extensions.Caching.Memory)的设计特性,并非Bug。
原因在于:当你在创建某一缓存条目的回调(比如MIXED的GetOrCreateAsync回调)中访问其他缓存条目时,IMemoryCache会自动为当前条目添加对这些依赖条目的关联。一旦依赖条目过期或被移除,当前条目也会被标记为过期。你的MIXED条目同时依赖了PERSON(1秒过期)和PLACE(5秒过期),因此它会跟随最早过期的PERSON条目一同失效。
如果想避免这种自动依赖,可通过以下方式处理:
- 在
FetchMixedAsync中直接调用FetchPersonAsync和FetchPlaceAsync,跳过缓存层 - 创建MIXED缓存条目时禁用自动依赖跟踪:在
cacheEntry上设置cacheEntry.SetOptions(new MemoryCacheEntryOptions { TrackDependencies = false })后再配置过期时间 - 手动管理依赖关系,比如只关联指定条目,或使用
CacheToken控制过期
测试代码如下:
public class Query { private readonly IMemoryCache _memoryCache; public Query(IMemoryCache memoryCache) { _memoryCache = memoryCache; } public async Task<Person> PersonAsync(CancellationToken cancellationToken) { return await _memoryCache.GetOrCreateAsync ( "PERSON", cacheEntry => { cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(1); return FetchPersonAsync(cancellationToken); } ).ConfigureAwait(false); } public async Task<Place> PlaceAsync(CancellationToken cancellationToken) { return await _memoryCache.GetOrCreateAsync ( "PLACE", cacheEntry => { cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5); return FetchPlaceAsync(cancellationToken); } ).ConfigureAwait(false); } public async Task<Mixed> MixedAsync(CancellationToken cancellationToken) { return await _memoryCache.GetOrCreateAsync ( "MIXED", cacheEntry => { cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60); return FetchMixedAsync(cancellationToken); } ).ConfigureAwait(false); } internal async Task<Person> FetchPersonAsync(CancellationToken cancellationToken) { await Task.CompletedTask.ConfigureAwait(false); return new Person { Name = Guid.NewGuid().ToString() }; } internal async Task<Place> FetchPlaceAsync(CancellationToken cancellationToken) { await Task.CompletedTask.ConfigureAwait(false); return new Place { Name = Guid.NewGuid().ToString() }; } internal async Task<Mixed> FetchMixedAsync(CancellationToken cancellationToken) { var person = await PersonAsync(cancellationToken).ConfigureAwait(false); var place = await PlaceAsync(cancellationToken).ConfigureAwait(false); return new Mixed { BaseLineName = person.Name, FacetName = place.Name, }; } }
内容的提问来源于stack exchange,提问作者Mark Rover2341
相关产品推荐
相关产品推荐

