SlidingExpiration和MemoryCache

Yoz*_*tic 18 c#

查看MemoryCache的文档,我预计如果在Expiration期间访问了一个对象,则会刷新该期间.说实话,我认为我从"滑动"这个名称推断出的东西和任何东西一样多.

但是,从这个测试看来

   [Test]
    public void SlidingExpiryNotRefreshedOnTouch()
    {
        var memoryCache = new MemoryCache("donkey")
        {
            {
                "1",
                "jane",
                new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) }
            }
        };
        var enumerable = Enumerable.Repeat("1", 100)
            .TakeWhile((id, index) =>
            {
                Thread.Sleep(100);
                return memoryCache.Get(id) != null; // i.e. it still exists
            })
            .Select((id, index) => (index+2)*100.0/1000); // return the elapsed time
        var expires = enumerable.Last(); // gets the last existing entry 
        expires.Should().BeGreaterThan(1.0);
    }
Run Code Online (Sandbox Code Playgroud)

它失败并显示一旦TimeSpan完成后对象被弹出的行为,无论对象是否已被访问.Linq查询在enumerable.Last()中执行; 语句,此时缓存尚未过期.一旦停止,列表中的最后一项将指示项目在缓存中存在多长时间.

对于Clarity这个问题是关于MemoryCache的行为.不是linq查询.

这是否是其他人的期望(即每次触摸时过期不会滑动)?是否有一种模式可以延长"触摸"对象的生命周期?

更新我发现即使我在缓存周围写了一个包装器,并且每次检索它时都将对象重新添加回缓存,而另一个SlidingExpiration仍然只支持初始设置.为了让它以我想要的方式工作,我必须在重新添加它之前将其从缓存中删除!这可能在多线程环境中导致不期望的竞争条件.

Han*_*ant 37

 ... new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) }
Run Code Online (Sandbox Code Playgroud)

这在MSDN中没有充分记录.你有点不走运,1秒还不够.通过头发,使用2秒,你会看到它的工作方式就像你希望的那样.使用FromMilliseconds()修补更多内容,你会发现~1.2秒是这个程序中最开心的.

解释这个问题相当复杂,我不得不谈谈MemoryCache如何避免每次访问缓存时都必须更新滑动计时器.正如您可能想象的那样,这是相对昂贵的.让我们走捷径,带您到相关的参考源代码.小到足以粘贴在这里:

    internal void UpdateSlidingExp(DateTime utcNow, CacheExpires expires) {
        if (_slidingExp > TimeSpan.Zero) {
            DateTime utcNewExpires = utcNow + _slidingExp;
            if (utcNewExpires - _utcAbsExp >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < _utcAbsExp) {
                expires.UtcUpdate(this, utcNewExpires);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

CacheExpires.MIN_UPDATE_DELTA是关键,它阻止调用UtcUpdate().换句话说,至少MIN_UPDATE_DELTA值的时间必须通过才会更新滑动计时器.CacheExpired类没有被Reference Source索引,暗示他们对它的工作方式并不完全满意:)但是一个体面的反编译器可以告诉你:

static CacheExpires()
{
    MIN_UPDATE_DELTA = new TimeSpan(0, 0, 1);
    MIN_FLUSH_INTERVAL = new TimeSpan(0, 0, 1);
    // etc...
}
Run Code Online (Sandbox Code Playgroud)

换句话说,硬编码为1秒.现在无法改变它,这非常难看.此测试程序中的SlidingExpiration值需要大约1.2秒,因为Thread.Sleep(100)实际上不会休眠100毫秒,需要更多时间.换句话说,它将是第11个Get()调用,它使滑动计时器在此测试程序中滑动.你没那么远.

嗯,这应该记录在案,但我猜这可能会有所改变.现在,您需要假设一个实际的滑动到期时间应该至少为2秒.

  • 这是一个很好的答案,也是stackoverflow功能的一个很好的例子.我知道1s的时间跨度并不是真正的代表性缓存示例,但显然希望最小化测试运行的时间.我还使用Timer和StopWatch来测试同样的事情(没有200ms的滞后和停止条件)但是认为包含的例子是最简单的展示问题.基本上任何> 1s 10ms的工作正如我预期的那样. (3认同)