Thread-safe cache libraries for .NET

后端 未结 4 797
一整个雨季
一整个雨季 2020-12-02 07:02

Background:

I maintain several Winforms apps and class libraries that either could or already do benefit from caching. I\'m also aware of the Cachi

4条回答
  •  再見小時候
    2020-12-02 08:01

    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 _htLocksByKey = new Dictionary();
        private static readonly Dictionary _htCollectionCache = new Dictionary();
    
        private static DateTime _dtLastPurgeCheck;
    
        public static List FetchAndCache(string sKey, Func> fGetCollectionDelegate) where T : IUniqueIdActiveRecord
        {
            List colItems = new List();
    
            lock (GetKeyLock(sKey))
            {
                if (_htCollectionCache.Keys.Contains(sKey) == true)
                {
                    CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                    colItems = (List) objCacheEntry.Collection;
                    objCacheEntry.LastAccess = DateTime.Now;
                }
                else
                {
                    colItems = fGetCollectionDelegate();
                    SaveCollection(sKey, colItems);
                }
            }
    
            List objReturnCollection = CloneCollection(colItems);
            return objReturnCollection;
        }
    
        public static List FetchAndCache(string sKey, Func> fGetCollectionDelegate)
        {
            List colIds = new List();
    
            lock (GetKeyLock(sKey))
            {
                if (_htCollectionCache.Keys.Contains(sKey) == true)
                {
                    CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                    colIds = (List)objCacheEntry.Collection;
                    objCacheEntry.LastAccess = DateTime.Now;
                }
                else
                {
                    colIds = fGetCollectionDelegate();
                    SaveCollection(sKey, colIds);
                }
            }
    
            List colReturnIds = CloneCollection(colIds);
            return colReturnIds;
        }
    
    
        private static List GetCollection(string sKey) where T : IUniqueIdActiveRecord
        {
            List 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)
                {
                    objReturnCollection = CloneCollection((List)objCacheEntry.Collection);
                }
            }
    
            return 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);
    
            lock (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);
    
            lock (GetKeyLock(sKey))
            {
                _htCollectionCache[sKey] = objCacheEntry;
            }
        }
    
        public static void UpdateCollection(string sKey, List 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();
    
                    //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(sKey, colItems);
                }
            }
        }
    
        public static void UpdateItem(string sKey, T objItem)  where T : IUniqueIdActiveRecord
        {
            lock (GetKeyLock(sKey))
            {
                if (_htCollectionCache.ContainsKey(sKey) == true)
                {
                    CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                    List 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) where T : IUniqueIdActiveRecord
        {
            lock (GetKeyLock(sKey))
            {
                if (_htCollectionCache.ContainsKey(sKey) == true)
                {
                    CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
                    List colCachedItems = (List)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(string sKey, T objItem) where T : IUniqueIdActiveRecord
        {
            lock (GetKeyLock(sKey))
            {
                List 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
        {
            lock (GetKeyLock(sKey))
            {
                Boolean bCollectionChanged = false;
    
                List objCollection = GetCollection(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(sKey, objCollection);
                }
            }
        }
    
        public static void AddItemToCollection(string sKey, T objItem) where T : IUniqueIdActiveRecord
        {
            lock (GetKeyLock(sKey))
            {
                List 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
        {
            lock (GetKeyLock(sKey))
            {
                List objCollection = GetCollection(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(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 colKeysToRemove = new List();
    
                    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 CloneCollection(List colItems) where T : IUniqueIdActiveRecord
        {
            List objReturnCollection = new List();
            //Clone the list - NEVER return the internal cache list
            if (colItems != null && colItems.Count > 0)
            {
                List colCachedItems = (List)colItems;
                foreach (T objItem in colCachedItems)
                {
                    objReturnCollection.Add(objItem);
                }
            }
            return objReturnCollection;
        }
    
        private static List CloneCollection(List colIds)
        {
            List colReturnIds = new List();
            //Clone the list - NEVER return the internal cache list
            if (colIds != null && colIds.Count > 0)
            {
                List colCachedItems = (List)colIds;
                foreach (Guid gId in colCachedItems)
                {
                    colReturnIds.Add(gId);
                }
            }
            return colReturnIds;
        } 
        #endregion
    
        #region Admin Functions
        public static List 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 GetResourcesByProject(Guid gProjectId)
        {
            String sKey = GetCacheKeyProjectResources(gProjectId);
            List colItems = CollectionCacheManager.FetchAndCache(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 GetResourcesByIds(Guid gProjectId, List 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.

提交回复
热议问题