我有一个非常简单的代码,但是它所做的非常奇怪。这是一个简单的缓存抽象,如下所示:
public class CacheAbstraction
{
private MemoryCache _cache;
public CacheAbstraction()
{
_cache = new MemoryCache(new MemoryCacheOptions { });
}
public async Task<T> GetItemAsync<T>(TimeSpan duration, Func<Task<T>> factory,
[CallerMemberName] string identifier = null ) where T : class
{
return await _cache.GetOrCreateAsync<T>(identifier, async x =>
{
x.SetAbsoluteExpiration(DateTime.UtcNow.Add(duration));
T result = null;
result = await factory();
return result;
});
}
}
有趣的是:我要通过1-1d的过期时间
如果我在测试套件中运行它,一切都很好。
如果我将其作为.net核心应用程序运行,则过期时间总是设置为“立即”,并且在下一次缓存检查时该项过期。哇!?
发布于 2022-04-06 20:15:16
我知道已经两年了,但我最近遇到了同样的问题(缓存项似乎立即过期),并发现了一个可能的原因。MemoryCache
中的两个本质上没有文档化的特性:链接缓存条目和options传播。
这允许子缓存条目对象在子缓存项超出作用域时被动地将其选项传播到父缓存项。这是通过IDisposable
完成的,ICacheEntry
实施器并在可拓方法 (如Set()
和GetOrCreate/Async()
)中由MemoryCache
在内部使用。这意味着,如果您有“嵌套”缓存操作,内部缓存将将其缓存条目选项传播到外部操作,包括取消令牌、过期回调和过期时间。
在我的例子中,我们使用的是GetOrCreateAsync()
和一个工厂方法,它使用一个库,它使用相同的注入IMemoryCache
进行自己的缓存。例如:
public async Task<Foo> GetFooAsync() {
return await _cache.GetOrCreateAsync("cacheKey", async c => {
c.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return await _library.DoSomething();
});
}
库在内部使用IMemoryCache
(通过DI注入的同一个实例)缓存结果几秒钟,实质上是这样做的:
_cache.Set(queryKey, queryResult, TimeSpan.FromSeconds(5));
由于GetOrCreateAsync()
已实现通过在using
块内创建CacheEntry
,因此库使用的5秒过期时间会传播到GetFooAsync()
中的父缓存项,从而导致Foo对象始终仅被缓存5秒而不是1小时,实际上会立即将其终止。
显示此行为的DotNet Fiddle:https://dotnetfiddle.net/fo14BT
您可以通过以下几种方式避免这种传播行为:
(1)用TryGetValue()
和Set()
代替GetOrCreateAsync()
if (_cache.TryGetValue("cacheKey", out Foo result))
return result;
result = await _library.DoSomething();
return _cache.Set("cacheKey", result, TimeSpan.FromHours(1));
(2)调用可能也使用缓存的其他代码后,分配缓存项选项
return await _cache.GetOrCreateAsync("cacheKey", async c => {
var result = await _library.DoSomething();
// set expiration *after*
c.AbsoluteExpiration = DateTime.Now.AddHours(1);
return result;
});
(自GetOrCreate/Async()
不能阻止重入以来,从并发的角度来看,两者实际上是相同的)。
警告:即使这样,也很容易出错。如果您尝试在选项(2)中使用AbsoluteExpirationRelativeToNow
,它将无法工作,因为设置该属性不会删除AbsoluteExpiration
值,如果该属性存在,则导致两个属性在CacheEntry
中都有一个值,并且AbsoluteExpiration
在相对项之前被授予。
未来,微软通过一个新的属性( property MemoryCacheOptions.TrackLinkedCacheEntries
)拥有MemoryCacheOptions.TrackLinkedCacheEntries
,但它要到.NET 7才能使用。如果没有这个未来的特性,除了使用不同的MemoryCache
实例之外,我还无法想出一种方法来阻止库的传播。
https://stackoverflow.com/questions/63426622
复制相似问题