.NET 的线程安全缓存库 [英] Thread-safe cache libraries for .NET

查看:28
本文介绍了.NET 的线程安全缓存库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:

我维护了几个 Winforms 应用程序和类库,它们可以或已经从缓存中受益.我也知道 缓存应用程序块System.Web.Caching 命名空间(从我的聚集,完全可以在 ASP.NET 之外使用).

我发现,尽管上述两个类在技术上都是线程安全的",即各个方法是同步的,但它们似乎并没有真正为多线程场景设计得特别好.具体来说,他们没有实现 GetOrAdd 方法 类似于 .NET 4.0 中新的 ConcurrentDictionary 类中的方法.

我认为这种方法是缓存/查找功能的原始,显然框架设计者也意识到了这一点——这就是这些方法存在于并发集合中的原因.然而,除了我还没有在生产应用中使用 .NET 4.0 的事实之外,字典并不是一个成熟的缓存——它没有过期、持久/分布式存储等功能.

<小时>

为什么这很重要:

富客户端"应用程序(甚至一些网络应用程序)中一个相当典型的设计是在应用程序启动后立即开始预加载缓存,如果客户端请求尚未加载的数据(随后缓存以备将来使用).如果用户快速地完成他的工作流程,或者如果网络连接很慢,那么客户端与预加载器竞争并不罕见,并且两次请求相同的数据确实没有多大意义,尤其是在请求相对昂贵的情况下.

所以我似乎还有一些同样糟糕的选择:

  • 根本不要尝试使操作原子化,并冒着数据被加载两次的风险(并且可能有两个不同的线程在不同的副本上运行);

  • 对缓存的访问序列化,这意味着锁定整个缓存只是为了加载单个项目

  • 开始重新发明轮子只是为了获得一些额外的方法.

<小时>

说明:时间线示例

假设当一个应用程序启动时,它需要加载 3 个数据集,每个数据集需要 10 秒的时间来加载.考虑以下两个时间表:

<前>00:00 - 开始加载数据集 100:10 - 开始加载数据集 200:19 - 用户要求数据集 2

在上面的例子中,如果我们不使用任何类型的同步,用户必须等待整整 10 秒才能获得 1 秒后可用的数据,因为代码会看到该项目尚未加载进入缓存并尝试重新加载它.

<前>00:00 - 开始加载数据集 100:10 - 开始加载数据集 200:11 - 用户请求数据集 1

在这种情况下,用户请求的是已经缓存中的数据.但是如果我们对缓存的访问进行序列化,他将不得不无缘无故地再等待 9 秒,因为缓存管理器(无论是什么)不知道被请求的特定项,只有某事"正在被请求并且某事"正在进行中.

<小时>

问题:

是否有任何适用于 .NET(4.0 之前)的缓存库确实实现这种原子操作,正如人们对线程安全缓存所期望的那样?

或者,是否有一些方法可以扩展现有的线程安全"缓存以支持此类操作,无需序列化对缓存的访问(这会破坏使用线程的目的-首先是安全实施)?我怀疑是否存在,但也许我只是累了,忽略了一个明显的解决方法.

或者……还有什么我想念的吗?如果两个相互竞争的线程碰巧同时请求相同的项目,第一次或到期后,让两个竞争线程相互推动是否只是标准做法?

解决方案

我知道您的痛苦,因为我是 的架构师之一去雾.我弄乱了很多缓存库,最终在经历了很多磨难之后才构建了这个库.此缓存管理器的一个假设是此类存储的所有集合都实现了一个接口,以获取 Guid 作为每个对象的Id"属性.由于这是针对 RIA,因此它包含许多用于从这些集合中添加/更新/删除项目的方法.

这是我的 CollectionCacheManager

公共类 CollectionCacheManager{私有静态只读对象 _objLockPeek = new object();private static readonly Dictionary_htLocksByKey = new Dictionary();private static readonly Dictionary_htCollectionCache = new Dictionary();私有静态日期时间 _dtLastPurgeCheck;公共静态列表<T>FetchAndCache(string sKey, Func> fGetCollectionDelegate) 其中 T : IUniqueIdActiveRecord{列表colItems = new List();锁 (GetKeyLock(sKey)){if (_htCollectionCache.Keys.Contains(sKey) == true){CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];colItems = (List) objCacheEntry.Collection;objCacheEntry.LastAccess = DateTime.Now;}别的{colItems = fGetCollectionDelegate();SaveCollection(sKey, colItems);}}列表objReturnCollection = CloneCollection(colItems);返回 objReturnCollection;}公共静态列表FetchAndCache(string sKey, Func> fGetCollectionDelegate){列表<指南>colIds = new List();锁 (GetKeyLock(sKey)){if (_htCollectionCache.Keys.Contains(sKey) == true){CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];colIds = (List)objCacheEntry.Collection;objCacheEntry.LastAccess = DateTime.Now;}别的{colIds = fGetCollectionDelegate();SaveCollection(sKey, colIds);}}列表<指南>colReturnIds = CloneCollection(colIds);返回 colReturnIds;}私有静态列表<T>GetCollection(string sKey) where T : IUniqueIdActiveRecord{列表objReturnCollection = null;if (_htCollectionCache.Keys.Contains(sKey) == true){CollectionCacheEntry objCacheEntry = null;锁 (GetKeyLock(sKey)){objCacheEntry = _htCollectionCache[sKey];objCacheEntry.LastAccess = DateTime.Now;}if (objCacheEntry.Collection != null && objCacheEntry.Collection is List){objReturnCollection = CloneCollection((List)objCacheEntry.Collection);}}返回 objReturnCollection;}public static void SaveCollection(string sKey, List colItems) where T : IUniqueIdActiveRecord{CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();objCacheEntry.Key = sKey;objCacheEntry.CacheEntry = DateTime.Now;objCacheEntry.LastAccess = DateTime.Now;objCacheEntry.LastUpdate = DateTime.Now;objCacheEntry.Collection = CloneCollection(colItems);锁 (GetKeyLock(sKey)){_htCollectionCache[sKey] = objCacheEntry;}}public static void SaveCollection(string sKey, List colIDs){CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();objCacheEntry.Key = sKey;objCacheEntry.CacheEntry = DateTime.Now;objCacheEntry.LastAccess = DateTime.Now;objCacheEntry.LastUpdate = DateTime.Now;objCacheEntry.Collection = CloneCollection(colIDs);锁 (GetKeyLock(sKey)){_htCollectionCache[sKey] = objCacheEntry;}}public static void UpdateCollection(string sKey, List colItems) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){if (_htCollectionCache.ContainsKey(sKey) == true){CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];objCacheEntry.LastAccess = DateTime.Now;objCacheEntry.LastUpdate = DateTime.Now;objCacheEntry.Collection = new List();//在插入之前克隆集合以确保它不能被触及foreach(colItems 中的 T objItem){objCacheEntry.Collection.Add(objItem);}_htCollectionCache[sKey] = objCacheEntry;}别的{SaveCollection(sKey, colItems);}}}public static void UpdateItem(string sKey, T objItem) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){if (_htCollectionCache.ContainsKey(sKey) == true){CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];列表colItems = (List)objCacheEntry.Collection;colItems.RemoveAll(o => o.Id == objItem.Id);colItems.Add(objItem);objCacheEntry.Collection = colItems;objCacheEntry.LastAccess = DateTime.Now;objCacheEntry.LastUpdate = DateTime.Now;}}}public static void UpdateItems(string sKey, List colItemsToUpdate) 其中 T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){if (_htCollectionCache.ContainsKey(sKey) == true){CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];列表colCachedItems = (List)objCacheEntry.Collection;foreach(colItemsToUpdate 中的 T objItem){colCachedItems.RemoveAll(o => o.Id == objItem.Id);colCachedItems.Add(objItem);}objCacheEntry.Collection = colCachedItems;objCacheEntry.LastAccess = DateTime.Now;objCacheEntry.LastUpdate = DateTime.Now;}}}public static void RemoveItemFromCollection(string sKey, T objItem) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){列表objCollection = GetCollection(sKey);if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0){objCollection.RemoveAll(o => o.Id == objItem.Id);UpdateCollection(sKey, objCollection);}}}public static void RemoveItemsFromCollection(string sKey, List colItemsToAdd) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){布尔 bCollectionChanged = false;列表objCollection = GetCollection(sKey);foreach(colItemsToAdd 中的 T objItem){if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0){objCollection.RemoveAll(o => o.Id == objItem.Id);bCollectionChanged = true;}}if (bCollectionChanged == true){UpdateCollection(sKey, objCollection);}}}public static void AddItemToCollection(string sKey, T objItem) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){列表objCollection = GetCollection(sKey);if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0){objCollection.Add(objItem);UpdateCollection(sKey, objCollection);}}}public static void AddItemsToCollection(string sKey, List colItemsToAdd) where T : IUniqueIdActiveRecord{锁 (GetKeyLock(sKey)){列表objCollection = GetCollection(sKey);布尔 bCollectionChanged = false;foreach(colItemsToAdd 中的 T objItem){if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0){objCollection.Add(objItem);bCollectionChanged = true;}}if (bCollectionChanged == true){UpdateCollection(sKey, objCollection);}}}public static void PurgeCollectionByMaxLastAccessInMinutes(int iMinutesSinceLastAccess){DateTime dtThreshHold = DateTime.Now.AddMinutes(iMinutesSinceLastAccess * -1);if (_dtLastPurgeCheck == null || dtThreshHold > _dtLastPurgeCheck){锁 (_objLockPeek){CollectionCacheEntry objCacheEntry;列表<字符串>colKeysToRemove = new List();foreach(_htCollectionCache.Keys 中的字符串 sCollectionKey){objCacheEntry = _htCollectionCache[sCollectionKey];如果(objCacheEntry.LastAccess (List colItems) 其中 T : IUniqueIdActiveRecord{列表objReturnCollection = new List();//克隆列表 - 永远不要返回内部缓存列表if (colItems != null && colItems.Count > 0){列表colCachedItems = (List)colItems;foreach(colCachedItems 中的 T objItem){objReturnCollection.Add(objItem);}}返回 objReturnCollection;}私有静态列表CloneCollection(List colIds){列表<指南>colReturnIds = new List();//克隆列表 - 永远不要返回内部缓存列表if (colIds != null && colIds.Count > 0){列表<指南>colCachedItems = (List)colIds;foreach(colCachedItems 中的 Guid gId){colReturnIds.Add(gId);}}返回 colReturnIds;}#endregion#region 管理功能公共静态列表GetAllCacheEntries(){返回 _htCollectionCache.Values.ToList();}public static void ClearEntireCache(){_htCollectionCache.Clear();}#endregion}公共密封类 CollectionCacheEntry{公共字符串键;公共日期时间缓存条目;公共日期时间上次更新;公共日期时间 LastAccess;公共 IList 集合;}

这是我如何使用它的示例:

公共静态类ResourceCacheController{#region 缓存方法公共静态列表<资源>GetResourcesByProject(Guid gProjectId){String sKey = GetCacheKeyProjectResources(gProjectId);列表<资源>colItems = CollectionCacheManager.FetchAndCache(sKey, delegate() { return ResourceAccess.GetResourcesByProject(gProjectId); });返回 colItems;}#endregion#region 缓存依赖方法public static int GetResourceCountByProject(Guid gProjectId){返回 GetResourcesByProject(gProjectId).Count;}公共静态列表<资源>GetResourcesByIds(Guid gProjectId, List colResourceIds){if (colResourceIds == null || colResourceIds.Count == 0){返回空;}返回 GetResourcesByProject(gProjectId).FindAll(objRes => colResourceIds.Any(gId => objRes.Id == gId)).ToList();}公共静态资源 GetResourceById(Guid gProjectId, Guid gResourceId){返回 GetResourcesByProject(gProjectId).SingleOrDefault(o => o.Id == gResourceId);}#endregion#region 缓存键和清除public static void ClearCacheProjectResources(Guid gProjectId){ CollectionCacheManager.ClearCollection(GetCacheKeyProjectResources(gProjectId));}公共静态字符串 GetCacheKeyProjectResources(Guid gProjectId){return string.Concat("ResourceCacheController.ProjectResources.", gP​​rojectId.ToString());}#endregion内部静态 void ProcessDeleteResource(Guid gProjectId, Guid gResourceId){资源 objRes = GetResourceById(gProjectId, gResourceId);如果(objRes != null){ CollectionCacheManager.RemoveItemFromCollection(GetCacheKeyProjectResources(gProjectId), objRes);}}内部静态 void ProcessUpdateResource(Resource objResource){CollectionCacheManager.UpdateItem(GetCacheKeyProjectResources(objResource.Id), objResource);}内部静态 void ProcessAddResource(Guid gProjectId, Resource objResource){CollectionCacheManager.AddItemToCollection(GetCacheKeyProjectResources(gProjectId), objResource);}}

这是有问题的接口:

公共接口 IUniqueIdActiveRecord{向导 ID { 获取;放;}}

希望这会有所帮助,我已经经历了几次地狱和回溯,终于找到了解决方案,对我们来说这是天赐之物,但我不能保证它是完美的,只是我们还没有找到一个问题.

Background:

I maintain several Winforms apps and class libraries that either could or already do benefit from caching. I'm also aware of the Caching Application Block and the System.Web.Caching namespace (which, from what I've gathered, is perfectly OK to use outside ASP.NET).

I've found that, although both of the above classes are technically "thread safe" in the sense that individual methods are synchronized, they don't really seem to be designed particularly well for multi-threaded scenarios. Specifically, they don't implement a GetOrAdd method similar to the one in the new ConcurrentDictionary class in .NET 4.0.

I consider such a method to be a primitive for caching/lookup functionality, and obviously the Framework designers realized this too - that's why the methods exist in the concurrent collections. However, aside from the fact that I'm not using .NET 4.0 in production apps yet, a dictionary is not a full-fledged cache - it doesn't have features like expirations, persistent/distributed storage, etc.


Why this is important:

A fairly typical design in a "rich client" app (or even some web apps) is to start pre-loading a cache as soon as the app starts, blocking if the client requests data that is not yet loaded (subsequently caching it for future use). If the user is plowing through his workflow quickly, or if the network connection is slow, it's not unusual at all for the client to be competing with the preloader, and it really doesn't make a lot of sense to request the same data twice, especially if the request is relatively expensive.

So I seem to be left with a few equally lousy options:

  • Don't try to make the operation atomic at all, and risk the data being loaded twice (and possibly have two different threads operating on different copies);

  • Serialize access to the cache, which means locking the entire cache just to load a single item;

  • Start reinventing the wheel just to get a few extra methods.


Clarification: Example Timeline

Say that when an app starts, it needs to load 3 datasets which each take 10 seconds to load. Consider the following two timelines:

00:00 - Start loading Dataset 1
00:10 - Start loading Dataset 2
00:19 - User asks for Dataset 2

In the above case, if we don't use any kind of synchronization, the user has to wait a full 10 seconds for data that will be available in 1 second, because the code will see that the item is not yet loaded into the cache and try to reload it.

00:00 - Start loading Dataset 1
00:10 - Start loading Dataset 2
00:11 - User asks for Dataset 1

In this case, the user is asking for data that's already in the cache. But if we serialize access to the cache, he'll have to wait another 9 seconds for no reason at all, because the cache manager (whatever that is) has no awareness of the specific item being asked for, only that "something" is being requested and "something" is in progress.


The Question:

Are there any caching libraries for .NET (pre-4.0) that do implement such atomic operations, as one might expect from a thread-safe cache?

Or, alternatively, is there some means to extend an existing "thread-safe" cache to support such operations, without serializing access to the cache (which would defeat the purpose of using a thread-safe implementation in the first place)? I doubt that there is, but maybe I'm just tired and ignoring an obvious workaround.

Or... is there something else I'm missing? Is it just standard practice to let two competing threads steamroll each other if they happen to both be requesting the same item, at the same time, for the first time or after an expiration?

解决方案

I know your pain as I am one of the Architects of Dedoose. I have messed around with a lot of caching libraries and ended up building this one after much tribulation. The one assumption for this Cache Manager is that all collections stored by this class implement an interface to get a Guid as a "Id" property on each object. Being that this is for a RIA it includes a lot of methods for adding /updating /removing items from these collections.

Here's my CollectionCacheManager

public class CollectionCacheManager
{
    private static readonly object _objLockPeek = new object();
    private static readonly Dictionary<String, object> _htLocksByKey = new Dictionary<string, object>();
    private static readonly Dictionary<String, CollectionCacheEntry> _htCollectionCache = new Dictionary<string, CollectionCacheEntry>();

    private static DateTime _dtLastPurgeCheck;

    public static List<T> FetchAndCache<T>(string sKey, Func<List<T>> fGetCollectionDelegate) where T : IUniqueIdActiveRecord
    {
        List<T> colItems = new List<T>();

        lock (GetKeyLock(sKey))
        {
            if (_htCollectionCache.Keys.Contains(sKey) == true)
            {
                CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                colItems = (List<T>) objCacheEntry.Collection;
                objCacheEntry.LastAccess = DateTime.Now;
            }
            else
            {
                colItems = fGetCollectionDelegate();
                SaveCollection<T>(sKey, colItems);
            }
        }

        List<T> objReturnCollection = CloneCollection<T>(colItems);
        return objReturnCollection;
    }

    public static List<Guid> FetchAndCache(string sKey, Func<List<Guid>> fGetCollectionDelegate)
    {
        List<Guid> colIds = new List<Guid>();

        lock (GetKeyLock(sKey))
        {
            if (_htCollectionCache.Keys.Contains(sKey) == true)
            {
                CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                colIds = (List<Guid>)objCacheEntry.Collection;
                objCacheEntry.LastAccess = DateTime.Now;
            }
            else
            {
                colIds = fGetCollectionDelegate();
                SaveCollection(sKey, colIds);
            }
        }

        List<Guid> colReturnIds = CloneCollection(colIds);
        return colReturnIds;
    }


    private static List<T> GetCollection<T>(string sKey) where T : IUniqueIdActiveRecord
    {
        List<T> objReturnCollection = null;

        if (_htCollectionCache.Keys.Contains(sKey) == true)
        {
            CollectionCacheEntry objCacheEntry = null;

            lock (GetKeyLock(sKey))
            {
                objCacheEntry = _htCollectionCache[sKey];
                objCacheEntry.LastAccess = DateTime.Now;
            }

            if (objCacheEntry.Collection != null && objCacheEntry.Collection is List<T>)
            {
                objReturnCollection = CloneCollection<T>((List<T>)objCacheEntry.Collection);
            }
        }

        return objReturnCollection;
    }


    public static void SaveCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
    {

        CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();

        objCacheEntry.Key = sKey;
        objCacheEntry.CacheEntry = DateTime.Now;
        objCacheEntry.LastAccess = DateTime.Now;
        objCacheEntry.LastUpdate = DateTime.Now;
        objCacheEntry.Collection = CloneCollection(colItems);

        lock (GetKeyLock(sKey))
        {
            _htCollectionCache[sKey] = objCacheEntry;
        }
    }

    public static void SaveCollection(string sKey, List<Guid> colIDs)
    {

        CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();

        objCacheEntry.Key = sKey;
        objCacheEntry.CacheEntry = DateTime.Now;
        objCacheEntry.LastAccess = DateTime.Now;
        objCacheEntry.LastUpdate = DateTime.Now;
        objCacheEntry.Collection = CloneCollection(colIDs);

        lock (GetKeyLock(sKey))
        {
            _htCollectionCache[sKey] = objCacheEntry;
        }
    }

    public static void UpdateCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            if (_htCollectionCache.ContainsKey(sKey) == true)
            {
                CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                objCacheEntry.LastAccess = DateTime.Now;
                objCacheEntry.LastUpdate = DateTime.Now;
                objCacheEntry.Collection = new List<T>();

                //Clone the collection before insertion to ensure it can't be touched
                foreach (T objItem in colItems)
                {
                    objCacheEntry.Collection.Add(objItem);
                }

                _htCollectionCache[sKey] = objCacheEntry;
            }
            else
            {
                SaveCollection<T>(sKey, colItems);
            }
        }
    }

    public static void UpdateItem<T>(string sKey, T objItem)  where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            if (_htCollectionCache.ContainsKey(sKey) == true)
            {
                CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                List<T> colItems = (List<T>)objCacheEntry.Collection;

                colItems.RemoveAll(o => o.Id == objItem.Id);
                colItems.Add(objItem);

                objCacheEntry.Collection = colItems;

                objCacheEntry.LastAccess = DateTime.Now;
                objCacheEntry.LastUpdate = DateTime.Now;
            }
        }
    }

    public static void UpdateItems<T>(string sKey, List<T> colItemsToUpdate) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            if (_htCollectionCache.ContainsKey(sKey) == true)
            {
                CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                List<T> colCachedItems = (List<T>)objCacheEntry.Collection;

                foreach (T objItem in colItemsToUpdate)
                {
                    colCachedItems.RemoveAll(o => o.Id == objItem.Id);
                    colCachedItems.Add(objItem);
                }

                objCacheEntry.Collection = colCachedItems;

                objCacheEntry.LastAccess = DateTime.Now;
                objCacheEntry.LastUpdate = DateTime.Now;
            }
        }
    }

    public static void RemoveItemFromCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            List<T> objCollection = GetCollection<T>(sKey);
            if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
            {
                objCollection.RemoveAll(o => o.Id == objItem.Id);
                UpdateCollection<T>(sKey, objCollection);
            }
        }
    }

    public static void RemoveItemsFromCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            Boolean bCollectionChanged = false;

            List<T> objCollection = GetCollection<T>(sKey);
            foreach (T objItem in colItemsToAdd)
            {
                if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
                {
                    objCollection.RemoveAll(o => o.Id == objItem.Id);
                    bCollectionChanged = true;
                }
            }
            if (bCollectionChanged == true)
            {
                UpdateCollection<T>(sKey, objCollection);
            }
        }
    }

    public static void AddItemToCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            List<T> objCollection = GetCollection<T>(sKey);
            if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
            {
                objCollection.Add(objItem);
                UpdateCollection<T>(sKey, objCollection);
            }
        }
    }

    public static void AddItemsToCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
    {
        lock (GetKeyLock(sKey))
        {
            List<T> objCollection = GetCollection<T>(sKey);
            Boolean bCollectionChanged = false;
            foreach (T objItem in colItemsToAdd)
            {
                if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
                {
                    objCollection.Add(objItem);
                    bCollectionChanged = true;
                }
            }
            if (bCollectionChanged == true)
            {
                UpdateCollection<T>(sKey, objCollection);
            }
        }
    }

    public static void PurgeCollectionByMaxLastAccessInMinutes(int iMinutesSinceLastAccess)
    {
        DateTime dtThreshHold = DateTime.Now.AddMinutes(iMinutesSinceLastAccess * -1);

        if (_dtLastPurgeCheck == null || dtThreshHold > _dtLastPurgeCheck)
        {

            lock (_objLockPeek)
            {
                CollectionCacheEntry objCacheEntry;
                List<String> colKeysToRemove = new List<string>();

                foreach (string sCollectionKey in _htCollectionCache.Keys)
                {
                    objCacheEntry = _htCollectionCache[sCollectionKey];
                    if (objCacheEntry.LastAccess < dtThreshHold)
                    {
                        colKeysToRemove.Add(sCollectionKey);
                    }
                }

                foreach (String sKeyToRemove in colKeysToRemove)
                {
                    _htCollectionCache.Remove(sKeyToRemove);
                }
            }

            _dtLastPurgeCheck = DateTime.Now;
        }
    }

    public static void ClearCollection(String sKey)
    {
        lock (GetKeyLock(sKey))
        {
            lock (_objLockPeek)
            {
                if (_htCollectionCache.ContainsKey(sKey) == true)
                {
                    _htCollectionCache.Remove(sKey);
                }
            }
        }
    }


    #region Helper Methods
    private static object GetKeyLock(String sKey)
    {
        //Ensure even if hell freezes over this lock exists
        if (_htLocksByKey.Keys.Contains(sKey) == false)
        {
            lock (_objLockPeek)
            {
                if (_htLocksByKey.Keys.Contains(sKey) == false)
                {
                    _htLocksByKey[sKey] = new object();
                }
            }
        }

        return _htLocksByKey[sKey];
    }

    private static List<T> CloneCollection<T>(List<T> colItems) where T : IUniqueIdActiveRecord
    {
        List<T> objReturnCollection = new List<T>();
        //Clone the list - NEVER return the internal cache list
        if (colItems != null && colItems.Count > 0)
        {
            List<T> colCachedItems = (List<T>)colItems;
            foreach (T objItem in colCachedItems)
            {
                objReturnCollection.Add(objItem);
            }
        }
        return objReturnCollection;
    }

    private static List<Guid> CloneCollection(List<Guid> colIds)
    {
        List<Guid> colReturnIds = new List<Guid>();
        //Clone the list - NEVER return the internal cache list
        if (colIds != null && colIds.Count > 0)
        {
            List<Guid> colCachedItems = (List<Guid>)colIds;
            foreach (Guid gId in colCachedItems)
            {
                colReturnIds.Add(gId);
            }
        }
        return colReturnIds;
    } 
    #endregion

    #region Admin Functions
    public static List<CollectionCacheEntry> GetAllCacheEntries()
    {
        return _htCollectionCache.Values.ToList();
    }

    public static void ClearEntireCache()
    {
        _htCollectionCache.Clear();
    }
    #endregion

}

public sealed class CollectionCacheEntry
{
    public String Key;
    public DateTime CacheEntry;
    public DateTime LastUpdate;
    public DateTime LastAccess;
    public IList Collection;
}

Here is an example of how I use it:

public static class ResourceCacheController
{
    #region Cached Methods
    public static List<Resource> GetResourcesByProject(Guid gProjectId)
    {
        String sKey = GetCacheKeyProjectResources(gProjectId);
        List<Resource> colItems = CollectionCacheManager.FetchAndCache<Resource>(sKey, delegate() { return ResourceAccess.GetResourcesByProject(gProjectId); });
        return colItems;
    } 

    #endregion

    #region Cache Dependant Methods
    public static int GetResourceCountByProject(Guid gProjectId)
    {
        return GetResourcesByProject(gProjectId).Count;
    }

    public static List<Resource> GetResourcesByIds(Guid gProjectId, List<Guid> colResourceIds)
    {
        if (colResourceIds == null || colResourceIds.Count == 0)
        {
            return null;
        }
        return GetResourcesByProject(gProjectId).FindAll(objRes => colResourceIds.Any(gId => objRes.Id == gId)).ToList();
    }

    public static Resource GetResourceById(Guid gProjectId, Guid gResourceId)
    {
        return GetResourcesByProject(gProjectId).SingleOrDefault(o => o.Id == gResourceId);
    }
    #endregion

    #region Cache Keys and Clear
    public static void ClearCacheProjectResources(Guid gProjectId)
    {            CollectionCacheManager.ClearCollection(GetCacheKeyProjectResources(gProjectId));
    }

    public static string GetCacheKeyProjectResources(Guid gProjectId)
    {
        return string.Concat("ResourceCacheController.ProjectResources.", gProjectId.ToString());
    } 
    #endregion

    internal static void ProcessDeleteResource(Guid gProjectId, Guid gResourceId)
    {
        Resource objRes = GetResourceById(gProjectId, gResourceId);
        if (objRes != null)
        {                CollectionCacheManager.RemoveItemFromCollection(GetCacheKeyProjectResources(gProjectId), objRes);
        }
    }

    internal static void ProcessUpdateResource(Resource objResource)
    {
        CollectionCacheManager.UpdateItem(GetCacheKeyProjectResources(objResource.Id), objResource);
    }

    internal static void ProcessAddResource(Guid gProjectId, Resource objResource)
    {
        CollectionCacheManager.AddItemToCollection(GetCacheKeyProjectResources(gProjectId), objResource);
    }
}

Here's the Interface in question:

public interface IUniqueIdActiveRecord
{
    Guid Id { get; set; }

}

Hope this helps, I've been through hell and back a few times to finally arrive at this as the solution, and for us It's been a godsend, but I cannot guarantee that it's perfect, only that we haven't found an issue yet.

这篇关于.NET 的线程安全缓存库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆