深入理解.NET MemoryCache (3)

通过Mono中的源代码可以看出,在CacheExpires内部使用了一个定时器,通过定时器触发定时的检查。在触发时使用的是CacheEntryCollection类的FlushItems方法。该方法的实现如下;

protected int FlushItems (DateTime limit, CacheEntryRemovedReason reason, bool blockInsert, int count = int.MaxValue) { var flushedItems = 0; if (blockInsert) store.BlockInsert (); lock (entries) { foreach (var entry in entries) { if (helper.GetDateTime (entry) > limit || flushedItems >= count) break; flushedItems++; } for (var f = 0; f < flushedItems; f++) store.Remove (entries.Min, null, reason); } if (blockInsert) store.UnblockInsert (); return flushedItems; }

在FlushItems(***)的逻辑中,通过遍历所有的缓存项并且比对了超时时间,将发现的超时缓存项执行Remove操作进行清理,实现缓存项的定期删除操作。通过Mono项目中该类的功能推断,在.net framework中的实现应该也是有类似的功能,即每一个MemoryCache的实例都会有一个负责定时检查的任务,负责处理掉所有超时的缓存项。

惰性删除

除了定时删除以外,MemoryCache还实现了惰性删除的功能,这项功能的实现相对于定时删除简单的多,而且非常的实用。

惰性删除是什么意思呢?简单的讲就是在使用缓存项的时候判断缓存项是否应该被删除,而不用等到被专用的清理任务清理。

前文描述过MemoryCache中数据的组织方式,既然是在使用时触发的逻辑,因此惰性删除必然与MemoryCacheStore获取缓存的方法有关。来看下它的Get方法的内部逻辑:

internal MemoryCacheEntry Get(MemoryCacheKey key) { MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry; // 判断是否超时 if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) { Remove(key, entry, CacheEntryRemovedReason.Expired); entry = null; } // 更新滑动超时的时间和相关的计数器 UpdateExpAndUsage(entry); return entry; }

从代码中可以看出,MemoryCacheStore查找到相关的key对应的缓存项以后,并没有直接返回,而是先检查了缓存项目的超时时间。如果缓存项超时,则删除该项并返回null。这就是MemoryCache中惰性删除的实现方式。

MemoryCache的缓存过期策略

向MemoryCache实例中添加缓存项的时候,可以选择三种:

永不超时

绝对超时

滑动超时

缓存策略在缓存项添加/更新缓存时(无论是使用Add或者Set方法)指定,通过在操作缓存时指定CacheItemPolicy对象来达到设置缓存超时策略的目的。

缓存超时策略并不能随意的指定,在MemoryCache内部对于CacheItemPolicy对象有内置的检查机制。先看下源码:

private void ValidatePolicy(CacheItemPolicy policy) { //检查过期时间策略的组合设置 if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration) { throw new ArgumentException(R.Invalid_expiration_combination, "policy"); } //检查滑动超时策略 if (policy.SlidingExpiration < ObjectCache.NoSlidingExpiration || OneYear < policy.SlidingExpiration) { throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "SlidingExpiration", ObjectCache.NoSlidingExpiration, OneYear)); } //检查CallBack设置 if (policy.RemovedCallback != null && policy.UpdateCallback != null) { throw new ArgumentException(R.Invalid_callback_combination, "policy"); } //检查优先级的设置 if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable) { throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "Priority", CacheItemPriority.Default, CacheItemPriority.NotRemovable)); } }

总结下源码中的逻辑,超时策略的设置有如下几个规则:

绝对超时和滑动超时不能同时存在(这是前文中说两者二选一的原因)

如果滑动超时时间小于0或者大于1年也不行

RemovedCallback和UpdateCallback不能同时设置

缓存的Priority属性不能是超出枚举范围(Default和NotRemovable)

MemoryCahce线程安全机制

根据MSDN的描述:。那么说明,在操作MemoryCache中的缓存项时,MemoryCache保证程序的行为都是原子性的,而不会出现多个线程共同操作导致的数据污染等问题。

那么,MemoryCache是如何做到这一点的?

MemoryCache在内部使用加锁机制来保证数据项操作的原子性。该锁以每个MemoryCacheStore为单位,即同一个MemoryCacheStore内部的数据共享同一个锁,而不同MemoryCacheStore之间互不影响。

存在加锁逻辑的有如下场景:

遍历MemoryCache缓存项

向MemoryCache添加/更新缓存项

执行MemoryCache析构

移除MemoryCache中的缓存项

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfpgd.html