首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >MemoryCache不接受任何过期期限

MemoryCache不接受任何过期期限
EN

Stack Overflow用户
提问于 2020-08-15 13:35:32
回答 1查看 220关注 0票数 1

我有一个非常简单的代码,但是它所做的非常奇怪。这是一个简单的缓存抽象,如下所示:

代码语言:javascript
运行
复制
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核心应用程序运行,则过期时间总是设置为“立即”,并且在下一次缓存检查时该项过期。哇!?

EN

回答 1

Stack Overflow用户

发布于 2022-04-06 20:15:16

我知道已经两年了,但我最近遇到了同样的问题(缓存项似乎立即过期),并发现了一个可能的原因。MemoryCache中的两个本质上没有文档化的特性:链接缓存条目和options传播

这允许子缓存条目对象在子缓存项超出作用域时被动地将其选项传播到父缓存项。这是通过IDisposable完成的,ICacheEntry 实施器并在可拓方法 (如Set()GetOrCreate/Async() )中由MemoryCache在内部使用。这意味着,如果您有“嵌套”缓存操作,内部缓存将将其缓存条目选项传播到外部操作,包括取消令牌、过期回调和过期时间。

在我的例子中,我们使用的是GetOrCreateAsync()和一个工厂方法,它使用一个库,它使用相同的注入IMemoryCache进行自己的缓存。例如:

代码语言:javascript
运行
复制
public async Task<Foo> GetFooAsync() {
    return await _cache.GetOrCreateAsync("cacheKey", async c => {
        c.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
        return await _library.DoSomething();
    });
}

库在内部使用IMemoryCache (通过DI注入的同一个实例)缓存结果几秒钟,实质上是这样做的:

代码语言:javascript
运行
复制
_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()

代码语言:javascript
运行
复制
if (_cache.TryGetValue("cacheKey", out Foo result))
    return result;

result = await _library.DoSomething();
return _cache.Set("cacheKey", result, TimeSpan.FromHours(1));

(2)调用可能也使用缓存的其他代码后,分配缓存项选项

代码语言:javascript
运行
复制
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实例之外,我还无法想出一种方法来阻止库的传播。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63426622

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档