上一篇文章介绍了Orchard中的缓存,本篇主要针对CacheManager进行分析,CacheManager在Orchard中用于存储应用程序的配置信息以及框架内部的一些功能支持,包括整个拓展及拓展监控都是基于Cache Manager的。Orchard的中的CacheManager也是非常有特色,仅提供了一个Get接口,缓存的过期是通过IVolatileToken接口实现的。

  先看一下和CacheManager的接口定义:

     public interface ICacheManager {
TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
ICache<TKey, TResult> GetCache<TKey, TResult>();
} public static class CacheManagerExtensions {
public static TResult Get<TKey, TResult>(this ICacheManager cacheManager, TKey key, bool preventConcurrentCalls, Func<AcquireContext<TKey>, TResult> acquire) {
if (preventConcurrentCalls) {
lock(key) {
return cacheManager.Get(key, acquire);
}
}
else {
return cacheManager.Get(key, acquire);
}
}
}

从上面代码可以看出来,它仅有个Get方法是通过一个Key和一个acquire委托实现的,Key代表缓存标识,acquire代表一个获取实际值的方法,换句话说通过Key在缓存中查找对象,如果找不到从acquire中获取。acquire接受一个以AcquireContext<TKey>为参数的委托。

用下面代码做下测试:

     public class MyController : Controller
{
private ICacheManager _cacheManager;
public MyController(ICacheManager cacheManager)
{
_cacheManager = cacheManager;
} public ActionResult Index()
{
var time1 = _cacheManager.Get("Time", ctx => { return DateTime.Now.ToString(); });
Thread.Sleep();
var time2 = _cacheManager.Get("Time", ctx => { return DateTime.Now.ToString(); });
return View();
}
}

最后发现time1和time2的结果一致,证明第一次获取缓存的时候是通过后面的方法创建的,获取了当前的时间,第二次的值和第一次一样,证明是从缓存中取出的。

但是要如何让缓存过期?接下来看一个完整的缓存用法:

    public class MyController : Controller
{
private ICacheManager _cacheManager;
private IClock _clock;
public MyController(ICacheManager cacheManager, IClock clock)
{
_cacheManager = cacheManager;
_clock = clock;
} public ActionResult Index()
{
var time1 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When( TimeSpan.FromSeconds()));
return DateTime.Now.ToString();
});
Thread.Sleep();
var time2 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When(TimeSpan.FromSeconds()));
return DateTime.Now.ToString();
});
Thread.Sleep();
var time3 = _cacheManager.Get("Time", ctx => {
ctx.Monitor(_clock.When(TimeSpan.FromSeconds()));
return DateTime.Now.ToString();
});
return View();
}
}

上面代码的结果就是,time1 = time2 != time3。 因为time3在获取缓存时已经过期了,所以返回了后面的时间。

相比最开始的代码仅多了一个ctx.Monitor的调用就是AcquireContext<TKey>这个参数起的作用,它是如何实现的?

     public interface IAcquireContext
{
Action<IVolatileToken> Monitor { get; }
} public class AcquireContext<TKey> : IAcquireContext
{
public AcquireContext(TKey key, Action<IVolatileToken> monitor)
{
Key = key;
Monitor = monitor;
} public TKey Key { get; private set; }
public Action<IVolatileToken> Monitor { get; private set; }
}

看上面代码可知AcquireContext(获取上下文)用于保存、映射缓存Key和它的监控委托。监控委托是一个返回值为IVolatileToken的方法。

    public interface IVolatileToken {
bool IsCurrent { get; }
}

IVolatileToken真正用来判断当前这个Key是否过期。

接下来看一下上面代码使用的Clock:

     /// <summary>
/// Provides the current Utc <see cref="DateTime"/>, and time related method for cache management.
/// This service should be used whenever the current date and time are needed, instead of <seealso cref="DateTime"/> directly.
/// It also makes implementations more testable, as time can be mocked.
/// </summary>
public interface IClock : IVolatileProvider
{
/// <summary>
/// Gets the current <see cref="DateTime"/> of the system, expressed in Utc
/// </summary>
DateTime UtcNow { get; } /// <summary>
/// Provides a <see cref="IVolatileToken"/> instance which can be used to cache some information for a
/// specific duration.
/// </summary>
/// <param name="duration">The duration that the token must be valid.</param>
/// <example>
/// This sample shows how to use the <see cref="When"/> method by returning the result of
/// a method named LoadVotes(), which is computed every 10 minutes only.
/// <code>
/// _cacheManager.Get("votes",
/// ctx => {
/// ctx.Monitor(_clock.When(TimeSpan.FromMinutes(10)));
/// return LoadVotes();
/// });
/// </code>
/// </example>
IVolatileToken When(TimeSpan duration); /// <summary>
/// Provides a <see cref="IVolatileToken"/> instance which can be used to cache some
/// until a specific date and time.
/// </summary>
/// <param name="absoluteUtc">The date and time that the token must be valid until.</param>
/// <example>
/// This sample shows how to use the <see cref="WhenUtc"/> method by returning the result of
/// a method named LoadVotes(), which is computed once, and no more until the end of the year.
/// <code>
/// var endOfYear = _clock.UtcNow;
/// endOfYear.Month = 12;
/// endOfYear.Day = 31;
///
/// _cacheManager.Get("votes",
/// ctx => {
/// ctx.Monitor(_clock.WhenUtc(endOfYear));
/// return LoadVotes();
/// });
/// </code>
/// </example>
IVolatileToken WhenUtc(DateTime absoluteUtc);
}

上面IClock接口的定义还附带了很详细的注释。

     public class Clock : IClock {
public DateTime UtcNow {
get { return DateTime.UtcNow; }
} public IVolatileToken When(TimeSpan duration) {
return new AbsoluteExpirationToken(this, duration);
} public IVolatileToken WhenUtc(DateTime absoluteUtc) {
return new AbsoluteExpirationToken(this, absoluteUtc);
} public class AbsoluteExpirationToken : IVolatileToken {
private readonly IClock _clock;
private readonly DateTime _invalidateUtc; public AbsoluteExpirationToken(IClock clock, DateTime invalidateUtc) {
_clock = clock;
_invalidateUtc = invalidateUtc;
} public AbsoluteExpirationToken(IClock clock, TimeSpan duration) {
_clock = clock;
_invalidateUtc = _clock.UtcNow.Add(duration);
} public bool IsCurrent {
get {
return _clock.UtcNow < _invalidateUtc;
}
}
}
}

现在已经大致可以猜出ICacheManager的过期是通过获取上下文中存储的这个IVolatileToken判断的。

最后来看一下CacheManager是如何存储缓存的,这里有一个疑问就是AcquireContext是如何存储的?如果不存储这个上下文,那么单纯的缓存对象就无法完成过期判断。

DefaulteCacheManager & CacheHolder:CacheHolder为实际的缓存存储介质。

     public class DefaultCacheHolder : ICacheHolder {
private readonly ICacheContextAccessor _cacheContextAccessor;
private readonly ConcurrentDictionary<CacheKey, object> _caches = new ConcurrentDictionary<CacheKey, object>(); public DefaultCacheHolder(ICacheContextAccessor cacheContextAccessor) {
_cacheContextAccessor = cacheContextAccessor;
} class CacheKey : Tuple<Type, Type, Type> {
public CacheKey(Type component, Type key, Type result)
: base(component, key, result) {
}
} /// <summary>
/// Gets a Cache entry from the cache. If none is found, an empty one is created and returned.
/// </summary>
/// <typeparam name="TKey">The type of the key within the component.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="component">The component context.</param>
/// <returns>An entry from the cache, or a new, empty one, if none is found.</returns>
public ICache<TKey, TResult> GetCache<TKey, TResult>(Type component) {
var cacheKey = new CacheKey(component, typeof(TKey), typeof(TResult));
var result = _caches.GetOrAdd(cacheKey, k => new Cache<TKey, TResult>(_cacheContextAccessor));
return (Cache<TKey, TResult>)result;
}
}

这里的要点是缓存介质是一个并发字典,存储内容为Cache类型的对象,它的Key是一个component(使用ICacheManager的当前类型)、Key的类型以及结果类型的一个三元组。

这里的Get方法返回的是一个ICache类型的对象,在CacheManager中是这样调用的:

         public ICache<TKey, TResult> GetCache<TKey, TResult>() {
return _cacheHolder.GetCache<TKey, TResult>(_component);
} public TResult Get<TKey, TResult>(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
return GetCache<TKey, TResult>().Get(key, acquire);
}

以及ICache:

     public interface ICache<TKey, TResult> {
TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire);
}

这样就发现最终acquire是在Cache对象中被使用的。

Cache & CacheEntry

     public class Cache<TKey, TResult> : ICache<TKey, TResult> {
private readonly ICacheContextAccessor _cacheContextAccessor;
private readonly ConcurrentDictionary<TKey, CacheEntry> _entries; public Cache(ICacheContextAccessor cacheContextAccessor) {
_cacheContextAccessor = cacheContextAccessor;
_entries = new ConcurrentDictionary<TKey, CacheEntry>();
} public TResult Get(TKey key, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = _entries.AddOrUpdate(key,
// "Add" lambda
k => AddEntry(k, acquire),
// "Update" lambda
(k, currentEntry) => UpdateEntry(currentEntry, k, acquire)); return entry.Result;
} private CacheEntry AddEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = CreateEntry(k, acquire);
PropagateTokens(entry);
return entry;
} private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
PropagateTokens(entry);
return entry;
} private void PropagateTokens(CacheEntry entry) {
// Bubble up volatile tokens to parent context
if (_cacheContextAccessor.Current != null) {
foreach (var token in entry.Tokens)
_cacheContextAccessor.Current.Monitor(token);
}
} private CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = new CacheEntry();
var context = new AcquireContext<TKey>(k, entry.AddToken); IAcquireContext parentContext = null;
try {
// Push context
parentContext = _cacheContextAccessor.Current;
_cacheContextAccessor.Current = context; entry.Result = acquire(context);
}
finally {
// Pop context
_cacheContextAccessor.Current = parentContext;
}
entry.CompactTokens();
return entry;
} private class CacheEntry {
private IList<IVolatileToken> _tokens;
public TResult Result { get; set; } public IEnumerable<IVolatileToken> Tokens {
get {
return _tokens ?? Enumerable.Empty<IVolatileToken>();
}
} public void AddToken(IVolatileToken volatileToken) {
if (_tokens == null) {
_tokens = new List<IVolatileToken>();
} _tokens.Add(volatileToken);
} public void CompactTokens() {
if (_tokens != null)
_tokens = _tokens.Distinct().ToArray();
}
}
}

从上面代码就可以看出,当实例化一个Cache的时候,_entries字段也是一个空的字典,在这种情况下一定调用AddEntry方法新建一个Entry添加到_entries中:

     private CacheEntry CreateEntry(TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = new CacheEntry();
var context = new AcquireContext<TKey>(k, entry.AddToken); IAcquireContext parentContext = null;
try {
// Push context
parentContext = _cacheContextAccessor.Current;
_cacheContextAccessor.Current = context; entry.Result = acquire(context);
}
finally {
// Pop context
_cacheContextAccessor.Current = parentContext;
}
entry.CompactTokens();
return entry;
}

entry.Resulte最终是通过acquire加上当前的上下文context创建的(注上面代码中ctx.Monitor这个方法实际上是entry.AddToken),换句话说ctx.Monitor(_clock.When( TimeSpan.FromSeconds(5)));这句代码是将_clock.When( TimeSpan.FromSeconds(5))返回的Token绑定到CacheEntry的_tokens字段上。

当获取一个已经存在的缓存对象时:

         private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
PropagateTokens(entry);
return entry;
}

将当前Entry上的所有Token拿出判断,如果都没有过期,那么就返回当前Entry,否则重新创建一个。

以上就是CacheManager的使用方法和它的实现原理,但是上面内容由一个一直没提到的东西ICacheContextAccessor _cacheContextAccessor;

它的默认实现如下,拥有一个线程静态的静态获取上下文属性:

     public class DefaultCacheContextAccessor : ICacheContextAccessor {
[ThreadStatic]
private static IAcquireContext _threadInstance; public static IAcquireContext ThreadInstance {
get { return _threadInstance; }
set { _threadInstance = value; }
} public IAcquireContext Current {
get { return ThreadInstance; }
set { ThreadInstance = value; }
}
}

在整个解决方案中搜索ICacheContextAccessor得到下面结果:

主要涉及它的对象有:Cache、DefaultCacheHolder和DefaulteParallelCacheContext。

针对这个问题,鉴于本篇已经较长,将再开一篇来说清楚它的作用。

Orchard详解--第五篇 CacheManager的更多相关文章

  1. Orchard详解--第六篇 CacheManager 2

    接上一篇,关于ICacheContextAccessor先看一下默认实现,用于保存一个获取上下文,且这个上下文是线程静态的: public class DefaultCacheContextAcces ...

  2. Orchard详解--第三篇 依赖注入之基础设施

    Orchard提供了依赖注入机制,并且框架的实现也离不开依赖注入如模块管理.日志.事件等.在前一篇中提到在Global.asax中定义的HostInitialization创建了Autofac的IoC ...

  3. Orchard详解--第八篇 拓展模块及引用的预处理

    从上一篇可以看出Orchard在处理拓展模块时主要有两个组件,一个是Folder另一个是Loader,前者用于搜索后者用于加载. 其中Folder一共有三个:Module Folder.Core Fo ...

  4. Orchard详解--第七篇 拓展模块(译)

    Orchard作为一个组件化的CMS,它能够在运行时加载任意模块. Orchard和其它ASP.NET MVC应用一样,支持通过Visual Studio来加载已经编译为程序集的模块,且它还提供了自定 ...

  5. Orchard详解--第四篇 缓存介绍

    Orchard提供了多级缓存支持,它们分别是: 1. 应用程序配置级缓存ICacheManager: 它用来存储应用程序的配置信息并且可以提供一组可扩展的参数来处理缓存过期问题,在Orchard中默认 ...

  6. Java中JNI的使用详解第五篇:C/C++中操作Java中的数组

    在Java中数组分为两种: 1.基本类型数组 2.对象类型(Object[])的数组(数组中存放的是指向Java对象中的引用) 一个能通用于两种不同类型数组的函数: GetArrayLength(ja ...

  7. [转]ANDROID L——Material Design详解(动画篇)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 转自:http://blog.csdn.net/a396901990/article/de ...

  8. IIS负载均衡-Application Request Route详解第四篇:使用ARR实现三层部署架构(转载)

    IIS负载均衡-Application Request Route详解第四篇:使用ARR实现三层部署架构 系列文章链接: IIS负载均衡-Application Request Route详解第一篇: ...

  9. appledoc导出iOS代码文档的使用和问题详解(干货篇)

    appledoc导出iOS代码文档的使用和问题详解(干货篇) 1. 简单说一下背景和自己感受 背景: 项目好像突然黄了,公司让详细写项目代码的注释并且导出文档,弄完之后就要封版. 说实话:听到这个消息 ...

随机推荐

  1. AI历史和哲学基础浅谈

    换个角度看AI:研究历史和哲学逻辑 正如题图所示,仿生人会梦见电子羊吗?(注:Do Androids Dream of Electric Sheep?是Philip K. Dick所著的一本科幻小说, ...

  2. Servlet JSP 二重修炼:Filter过滤器

    摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 真正的朋友就是,当你蒙蔽了所有人的眼睛,也能看穿你真实的样子和心底的痛楚 ...

  3. iOS逆向开发(4):注入目标函数 | fishhook | MobileSubstrate | MSHookFunction | iOSOpenDev

    从获得APP的所有类声明,到锁定目标类与函数,现在是时候注入函数了. 所谓"注入函数",小程的意思是让APP执行到小程写的代码中,跟"钩子"的概念一致.小程把个 ...

  4. istio小结

    一.概述 测试环境已经跑了很长时间的istio了,也更新到了最新的istio-1.1.性能相较之前提升很大,官方给出的测试数据说是延迟降低到了8ms,但是实际测试确实访问速度有很大的提升,但是确实还是 ...

  5. Apache Solr 实现去掉重复的搜索结果

    https://lucene.apache.org/solr/guide/7_2/collapse-and-expand-results.html#collapsing-query-parser 对应 ...

  6. MyBatis源码解析(六)——DataSource数据源模块之池型数据源

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675674.html 1 回顾 上一文中解读了MyBatis中非池型数据源的源码,非池型也 ...

  7. 跨域 webpack + vue-cil 中 proxyTable 处理跨域

    博客地址:https://ainyi.com/27 跨域 了解同源政策:所谓"同源"指的是"三个相同". 协议相同 域名相同 端口相同 解决跨域 jsonp 缺 ...

  8. 【golang-GUI开发】qt之signal和slot(二)

    上一篇文章里我们详细介绍了signal的用法. 今天我们将介绍slot的使用.在qt中slot和signal十分相像,这次我们将实现一个能显示16进制数字的SpinBox,它继承自QSpinbox并重 ...

  9. 【转载】Window服务器开机后一直处于蓝色屏幕(非蓝屏 crash)状态

    阿里云Windows系统服务器运维的过程中,有时候会遇到实例开机后一直处于蓝色背景屏幕(非蓝屏 crash )状态.此时你发现鼠标可以任意正常移动,但是屏幕上却没有任何的图标可以供操作,这种情况可能是 ...

  10. python 反射机制在实际的应用场景讲解

    剖析python语言中 "反射" 机制的本质和实际应用场景一. 前言 def s1(): print("s1是这个函数的名字!") s = "s1&q ...