zoukankan      html  css  js  c++  java
  • Orchard源码分析

        ICacheManager  &   ICacheHolder             

    Orchard缓存管理主要通过 ICacheManager 接口对外提供缓存服务. 其实现类DefaultCacheManager的构造函数如下,

    DefaultCacheManager(Type component/*此类型是缓存服务消费者的类型名称,在CacheModule中配置注册,
    其主要作用是在CacheHolder中创建一个cacheKey,cacheKey使用三元组(Tuple)数组结构,使cacheKey免于冲突,(component类型+用户Key类型+返回值类型)保证cacheKey的唯一性*/
    , ICacheHolder cacheHolder)

    ICache对象[最小缓存单元]由DefaultCacheHolder类提供,此类内部使用一个线程安全的ConcurrentDictionary来缓存Cache对象[Cache对象为最小缓存单元],此字典就是Orchard缓存的最终存储位置 !

    此字典使用三元组(Tuple)做为cache避免key冲突. 获取Cache的原理是:

    1. 如果在此字典中用cacheKey[上面说的三元组结构]取到值则直接返回.

    2. 如果未取到则new Cache .(此处硬编码写死了ICache的实现类,如果想使用其它Cache实现则必须修改此处)

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

        ICache (最小缓存单元)                                

    此接口的实现类是Orchard中的一个最小缓存单元. 但此缓存单元并不是直接拿来使用的object,而是需要调用Get(key, acquire)才能获取想要的缓存值 .

    Cache类同样使用了线程安全的ConcurrentDictionary来存储键值数据,和DefaultCacheHolder不同的是: 一个Cache对象中,此字典仅有一对键值项.

    Cache类Get缓存值的原理是 :

    1. 如果用key在ConcurrentDictionary中未取到值,那么将创建一个CacheEntry

    2. 如果用key在ConcurrentDictionary中取到值,则更新此CacheEntry

    private CacheEntry UpdateEntry(CacheEntry currentEntry, TKey k, Func<AcquireContext<TKey>, TResult> acquire) {
                //在此检测currentEntry中令牌列表中是否有任一令牌过期,如果有一个过期了,则重建CacheEntry,否则CacheEntry保持原样.
                var entry = (currentEntry.Tokens.Any(t => t != null && !t.IsCurrent)) ? CreateEntry(k, acquire) : currentEntry;
                PropagateTokens(entry);
                return entry;
    }

    注意此key不是上面说的三元组结构的Key,而是用户定义的泛型key !

    CacheEntry是一个Cache的内部私有类,CacheEntry中的Result属性为最终有用的值,Cache.Get方法的返回结果就是Result. (千呼万唤始出来..... 很不容易).

       CacheEntry的作用                                       

    Orchard缓存的最终值被包装在CacheEntry中,此类内部维护了一个IVolatileToken链表,IVolatileToken是一个过期令牌(内部有一个IsCurrent属性,如果为false则过期,实施过期的代码在UpdateEntry方法中),

    1.创建CacheEntry对象时,将回调acquire(context)方法来设置Result属性(即最终缓存值). 这里面要注意context参数,context参数是随着CacheEntry实例创建时一个新的AcquireContext实例,

    然后通过AcquireContext构造函数做了一个重要的操作,伪代码为:

    context.Monitor=CacheEntry.AddToken

    使用缓存的时候,可以调用 Monitor 来将令牌添加到内部链表中.(以下代码风格在Orchard中极为常见)

    public IEnumerable<StereotypeDescription> GetStereotypes() {
                return _cacheManager.Get("ContentType.Stereotypes", context => {
                    //此处调用Monitor方法的本质是在对应的CacheEntry中的令牌列表中添加一个令牌项
                    context.Monitor(_clock.WhenUtc(_clock.UtcNow.AddMinutes(1)));
                    return _providers.SelectMany(x => x.GetStereotypes());
                });
                
            }

    2.当回调完成后,将调用CacheEntry.CompactTokens方法来将过期令牌去重

       如何创建过期令牌                                        

    namespace Orchard.Caching {
        //所有实现此空接口的类都被视为令牌提供器
        public interface IVolatileProvider : ISingletonDependency {
        }
    }

    注: 在1.8版本及以前版本 IVolatileProvider接口未被明显使用, 仅标识类型. (将来的版本中可能会利用此空接口做一些自动注入的操作,目前版本仍然是手工注入)

    在EnvironmentOrchardStart.cs类中注册了所有提供器

         RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
         RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
         RegisterVolatileProvider<DefaultLockFileManager, ILockFileManager>(builder);
         RegisterVolatileProvider<Clock, IClock>(builder);
         RegisterVolatileProvider<DefaultDependenciesFolder, IDependenciesFolder>(builder);
         RegisterVolatileProvider<DefaultExtensionDependenciesManager, IExtensionDependenciesManager>(builder);
         RegisterVolatileProvider<DefaultAssemblyProbingFolder, IAssemblyProbingFolder>(builder);
         RegisterVolatileProvider<DefaultVirtualPathMonitor, IVirtualPathMonitor>(builder);
         RegisterVolatileProvider<DefaultVirtualPathProvider, IVirtualPathProvider>(builder);

       分析一个最简单的Signals(信号量)              

    这是一个最具代表性的过期令牌提供器,在内部使用一个Dictionary来保存所有<键,Token>项,(因为可能有多个服务消费者,但是此类是单例).

    1. 当调用 When<T>(T signal) 时,在Dictionary中查找或添加一个Token实例, 用以启用信号量监控.(Token为内部类,实现IVolatileToken,接口. 初始时IsCurrent=true),

    此Token被添加到CacheEntry的内部链表中 ,因为是引用类型,当Signals内的字典中的值改变的时候,CacheEntry中的链表值也会改变(引用相同对象, .NET基础知识).

    _cacheDuration = _cacheManager.Get("CacheSettingsPart.Duration",
                    context => {
                        //监视一个信号,如果此信号被Trigger(触发),则当前令牌被标识过期
                        context.Monitor(_signals.When(CacheSettingsPart.CacheKey));
                        return _workContext.CurrentSite.As<CacheSettingsPart>().DefaultCacheDuration;
                    }
                );

    2. 当程序另一部分调用此类Trigger方法时,将在Dictionary移除指定的Token并且设置此Token的IsCurrent=false, 标识过期.

    //标识令牌过期
    _signals.Trigger("MediaProcessing_Saved_" + filter.ImageProfilePartRecord.Name);

    引用相等的示例( 基础知识 )

    void Main()
    {
        var a=new Person{N="我是a"};
        var b=new Person{N="我是b"};
        var c=new Person{N="我是c"};
        var dic=new Dictionary<string,object>();
        dic.Add("a",a);
        dic.Add("b",b);
        dic.Add("c",c);
        var list=new List<Object>();
        list.Add(a);
        list.Add(b);
        list.Add(c);
        object o;
        dic.TryGetValue("a",out o);
        ((Person)o).N="你好";
        //字典中与链表中的值  引用是相等的
        Console.Write(System.Object.ReferenceEquals(o,list[0]));   //输出 True
        //因此改变字典中的值, 链表的值也会改变
        Console.Write(list[0].N);   //输出 "你好"
    }
    class Person
    {
    public string N;
    }

       IAsyncTokenProvider(异步令牌提供器)             

    DefaultAsyncTokenProvider类内部有个子类 AsyncVolativeToken : IVolatileToken , 在获取令牌(GetToken)时 , 传递一个Action<Action<IVolatileToken>> task参数 ,

    此 task将在线程池中异步执行, 将token => _taskTokens.Add(token) 作为参数传递给方法体

    public void QueueWorkItem() {
                    // Start a work item to collect tokens in our internal array
                    ThreadPool.QueueUserWorkItem(state => {
                        try {
                            _task(token => _taskTokens.Add(token));
                        }
                        catch (Exception e) {
                            Logger.Error(e, "Error while monitoring extension files. Assuming extensions are not current.");
                            _taskException = e;
                        }
                        finally {
                            _isTaskFinished = true;
                        }
                    });
                }
    extensionEntry = _cacheManager.Get(extensionId, ctx => {
                        var entry = BuildEntry(extensionDescriptor);
                        if (entry != null) {
                            ctx.Monitor(_asyncTokenProvider.GetToken(monitor => {
    //此方法体将在线程池中异步执行
    //monitor调用时将会向AsyncVolativeToken实例的内部的List<IVolatileToken>中添加token
                                foreach (var loader in _loaders) {
                                    loader.Monitor(entry.Descriptor, token => monitor(token));
                                }
                            }));
                        }
                        return entry;
                    });

       在缓存获取方法中使用并行                          

    先来看看什么是缓存获取方法:

    _cacheManager.Get("CacheSettingsPart.Duration",
                    context => {
                      //这个方法仅在Cache中ConcurrentDictionary字典中未找到缓存项才执行,
                       //我称此方法体为[ 缓存获取方法体 ]
                       return  XXXXXX;
                    }
                );

    如果需要在此方法中使用并行执行提高程序效率,那么Orchard 提供了一个IParallelCacheContext 接口,其中有个方法为

    IEnumerable<TResult> RunInParallel<T, TResult>(IEnumerable<T> source, Func<T, TResult> selector);

    此方法可以独立使用,  也可以在缓存获取方法中使用.

    -----------------------------------------

    此方法是将 T类型的source序列  中的元素通过selector方法投影到新的   TResult类型对象中 .

    T  -> 使用PLINQ 并行执行 –> TResult

    具体过程是:

    1. 首先判断Disabled属性是否为禁用状态,如果禁用则用 LINQ直接投影,并不会并行执行

    2. 通过内部公开类 TaskWithAcquireContext 包装原始序列中的所有元素, 构造函数中传递投影元素的 lamada

    然后用第二步中的投影结果 再 并行执行, 每个元素都会调用  Execute方法

    // Run tasks in parallel and combine results immediately
    var result = tasks
     .AsParallel() // prepare for parallel execution
     .AsOrdered() // preserve initial enumeration order
     .Select(task => task.Execute()) // prepare tasks to run in parallel
    .ToArray(); // force evaluation

    那么调用Execute方法会做什么事情呢,下面我们要分两种情况:

    1. 在缓存获取方法中使用RunInParallel

    6D%5GXM2USEOV2[9@HF2J0X

    我们可以看到,如果在缓存方法体里面调用RunInParallel,  _cacheContextAccessor.Current是有值的, _cacheContextAccessor.Current.Monitor 指向当前CacheEntry中的AddToken方法.

    未完待续。。。。。。。

  • 相关阅读:
    1295. 统计位数为偶数的数字『简单』
    1281. 整数的各位积和之差『简单』
    697. 数组的度『简单』
    748. 最短完整词『简单』
    832. 翻转图像『简单』
    1446. 连续字符『简单』
    1455. 检查单词是否为句中其他单词的前缀『简单』
    1160. 拼写单词『简单』
    1304. 和为零的N个唯一整数『简单』
    1103. 分糖果 II『简单』
  • 原文地址:https://www.cnblogs.com/cabbage/p/3730051.html
Copyright © 2011-2022 走看看