如果缓存已满,第二次调用 MemoryCache.Set() 使用相同的键擦除条目

2nd call to MemoryCache.Set() with the same key erases entry if cache is full

这有点边缘情况,如果我能找到它,我会将其作为错误提交到 repo 中...

考虑以下 LINQPad snippet

void Main()
{
    var memoryCache = new MemoryCache(new MemoryCacheOptions
    {
        SizeLimit = 1 // <-- Setting to 2 fixes the issue
    });

    Set(memoryCache);
    memoryCache.Get("A").Dump(); // Yields 1
    Set(memoryCache);
    memoryCache.Get("A").Dump(); // Yields null
}

private void Set(MemoryCache memoryCache)
{
    //memoryCache.Remove("A"); // <-- Also fixes the issue

    memoryCache.Set("A", 1, new MemoryCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1), 
        SlidingExpiration = TimeSpan.FromDays(1), 
        Size = 1
    });
}

我的问题是,当使用 .Set() 时,是否添加了新条目,然后删除了旧条目,因此需要在缓存中分配额外的 space?

这似乎与 bug I logged (which was rejected). You can see the source for this here 有关,根据我的阅读,您(和我)案例中的逻辑是这样的:

  • 请求添加项目。
  • 如果要添加项目将超出大小 (UpdateCacheSizeExceedsCapacity()) 因此拒绝请求(默默地,这是我反对的)。
  • 此外,由于检测到这种情况,请启动 OvercapacityCompaction(),这将删除您的项目。

似乎存在竞争条件,因为压缩工作排队到后台线程;也许偶尔您的测试会发现该项目仍然存在?

回答你的具体问题 - 不,它不是先添加然后删除多余的。

编辑:

关于第二个 Get() 总是返回空...我错过了对现有条目的一些额外处理,如果找到的话(虽然它不会改善结果)。在检查添加项目是否超过尺寸之前,有这样的:

if (_entries.TryGetValue(entry.Key, out CacheEntry priorEntry))
{
    priorEntry.SetExpired(EvictionReason.Replaced);
}

即如果它找到您现有的条目,它会将其标记为已驱逐。然后它应用 UpdateCacheSizeExceedsCapacity() 测试,没有考虑它只是驱逐了现有条目(可以说它可以)。

稍后,仍然在Set()/SetEntry(),在超出容量的情况下,它会这样做:

if (priorEntry != null)
{
    RemoveEntry(priorEntry);
}

...这会立即删除之前的条目。所以 OvercapacityCompaction()(或 ScanForExpiredItems())是否以及何时会得到它并不重要,它在从 Set()/SetEntry().

返回之前就消失了

(我还将上面的来源 link 更新为当前值;不改变逻辑)。