【问题标题】:SlidingExpiration and MemoryCacheSlidingExpiration 和 MemoryCache
【发布时间】:2016-12-30 11:23:57
【问题描述】:

查看MemoryCache 的文档,我预计如果在到期期限内访问对象,则该期限将被刷新。老实说,我想我是从“滑动”这个名字中推断出来的。

但是,从这个测试中可以看出

   [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);
    }

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

为了清晰 这个问题是关于 MemoryCache 的行为。不是 linq 查询。

这是其他人的期望吗(即到期不会随着每次触摸而滑动)? 是否有一种模式可以延长被“触摸”的对象的生命周期?

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

【问题讨论】:

  • 也许你想把这个问题分成两半? MemoryCache 使用有问题,还是 Linq 使用有问题?

标签: c#


【解决方案1】:
 ... new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) }

这在 MSDN 中没有充分记录。你有点倒霉,1秒不够。通过一根头发,使用 2 秒,您会看到它的工作原理与您希望的一样。用 FromMilliseconds() 再修改一下,你会发现 ~1.2 秒是这个程序中最快乐的最小值。

解释这一点相当复杂,我必须谈谈 MemoryCache 如何避免每次访问缓存时都必须更新滑动计时器。正如您可能想象的那样,这是相对昂贵的。让我们走捷径,带你到相关的Reference Source code。小到可以粘贴在这里:

    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);
            }
        }
    }

CacheExpires.MIN_UPDATE_DELTA 是关键,它阻止了 UtcUpdate() 被调用。或者换一种说法,在更新滑动计时器之前,至少要经过 MIN_UPDATE_DELTA 的时间。 CacheExpired 类没有被引用源索引,这暗示他们对它的工作方式并不完全满意 :) 但是一个体面的反编译器可以告诉你:

static CacheExpires()
{
    MIN_UPDATE_DELTA = new TimeSpan(0, 0, 1);
    MIN_FLUSH_INTERVAL = new TimeSpan(0, 0, 1);
    // etc...
}

换句话说,硬编码为 1 秒。现在没有办法改变它,这很丑陋。此测试程序中的 SlidingExpiration 值大约需要 1.2 秒,因为 Thread.Sleep(100) 实际上并没有休眠 100 毫秒,它需要更多时间。或者换句话说,将是第 11 次 Get() 调用使滑动计时器在此测试程序中滑动。你没有走那么远。

嗯,这应该记录在案,但我猜这可能会发生变化。现在,您需要假设实际滑动过期时间至少应为 2 秒。

【讨论】:

  • 这是一个很好的答案,也是 stackoverflow 强大功能的一个很好的例子。我知道 1 秒的时间跨度并不是一个真正具有代表性的缓存示例,但显然希望最小化测试运行的时间。我还使用了 Timer 和 StopWatch 来测试相同的东西(没有 200ms 的滞后和停止条件)但认为包含的示例是最简单的问题。基本上任何大于 1s 10ms 的东西都可以按我的预期工作。
猜你喜欢
  • 2016-02-15
  • 1970-01-01
  • 2021-08-24
  • 1970-01-01
  • 1970-01-01
  • 2014-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多