2009-06-25 59 views
2

我需要创建一个Web模块,这个模块我需要获取一些网站的标题,我会找到那个标题,我需要存储在线程安全缓存机制中,我需要在那里保存10个拉取标题。线程安全缓存机制(而不是.NET内置缓存)ASPX C#

请帮忙吗?

+0

尽管我有点喜欢拼写错误的.NET(.NOT),为了清晰起见,我改变了它。 – 2009-06-25 17:21:36

+0

你在问什么?你正在寻找一个线程安全的缓存? – 2009-06-25 17:23:29

回答

4

写一些锁定代码将是相当容易的,除了...

你想如何检索它?你希望能够以线程安全的方式列举(foreach)列表吗?有很多不同的方法可以做到这一点,每一种方式都需要权衡。

您可以使用默认行为 这可能无法正常工作 - 如果有人在列举时更改列表,则会发生异常。

您可以在整个枚举过程中锁定集合。这意味着试图添加到缓存的任何线程都将被阻塞,直到foreach循环退出。

您可以在每次枚举集合并枚举副本时在内部复制集合。 这意味着如果有人在列举时添加到列表中,则不会“看到”更改。

对于十个列表,我会选择最后一个选项。 (内部复制)。

你的代码会是这个样子:

 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Enumerator 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyCache<string> cache = new MyCache<string>(); 
      cache.Add("test"); 
      foreach (string item in cache) 
       Console.WriteLine(item); 
      Console.ReadLine(); 
     } 
    } 

    public class MyCache<T>: System.Collections.IEnumerable 
    { 
     private readonly LinkedList<T> InternalCache = new LinkedList<T>(); 
     private readonly object _Lock = new Object(); 

     public void Add(T item) 
     { 
      lock (_Lock) 
      { 
       if (InternalCache.Count == 10) 
        InternalCache.RemoveLast(); 
       InternalCache.AddFirst(item); 
      } 
     } 

     #region IEnumerable Members 

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
     { 
      // copy the internal cache to an array. We'll really be enumerating that array 
      // our enumeration won't block subsequent calls to Add, but this "foreach" instance won't see Adds either 
      lock (_Lock) 
      { 
       T[] enumeration = new T[InternalCache.Count]; 
       InternalCache.CopyTo(enumeration, 0); 
       return enumeration.GetEnumerator(); 
      } 
     } 

     #endregion 

    } 

} 
 

< B>编辑1:</B> 后罗布·莱文(下)分享一些意见,我想我会扔相邻的一在那里的替代品。

该版本允许您无锁地重复收集。但是,Add()方法稍微昂贵一些,因为它必须复制列表(将枚举的费用从枚举移出并添加到添加中)。

 

    public class Cache2<T>: IEnumerable<T> 
    { 
     // changes occur to this list, and it is copied to ModifyableList 
     private LinkedList<T> ModifyableList = new LinkedList<T>(); 

     // This list is the one that is iterated by GetEnumerator 
     private volatile LinkedList<T> EnumeratedList = new LinkedList<T>(); 

     private readonly object LockObj = new object(); 

     public void Add(T item) 
     { 
      // on an add, we swap out the list that is being enumerated 
      lock (LockObj) 
      { 
       if (this.ModifyableList.Count == 10) 
        this.ModifyableList.RemoveLast(); 

       this.ModifyableList.AddFirst(item); 
       this.EnumeratedList = this.ModifyableList; 
       // the copy needs to happen within the lock, so that threaded calls to Add() remain consistent 
       this.ModifyableList = new LinkedList<T>(this.ModifyableList); 
      } 

     } 

     #region IEnumerable<T> Members 

     IEnumerator<T> IEnumerable<T>.GetEnumerator() 
     { 
      IEnumerable<T> enumerable = this.EnumeratedList; 
      return enumerable.GetEnumerator(); 
     } 

     #endregion 

     #region IEnumerable Members 

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
     { 
      System.Collections.IEnumerable enumerable = this.EnumeratedList; 
      return enumerable.GetEnumerator(); 
     } 

     #endregion 
    } 
 

< B>编辑2:</B> 在最后一个例子,我们有一个非常便宜的迭代器,权衡是一个更昂贵的呼叫添加()。接下来,我想了解如何使用ReaderWriterLockSlim(这是一个.Net 3.5对象 - 旧的ReaderWriterLock提供的性能很差)

使用此模型,Add()方法比以前的模型便宜(尽管Add still必须采取排他锁)。有了这个模型,我们不必创建列表的副本。当我们枚举列表时,我们输入一个readlock,它不会阻止其他读者,但阻止/被作者阻止(调用Add)。至于哪种模式更好 - 这可能取决于您如何使用缓存。我会推荐测试和测量。

 

    public class Cache3<T> : IEnumerable<T> 
    { 
     private LinkedList<T> InternalCache = new LinkedList<T>(); 
     private readonly System.Threading.ReaderWriterLockSlim LockObj = new System.Threading.ReaderWriterLockSlim(); 

     public void Add(T item) 
     { 
      this.LockObj.EnterWriteLock(); 
      try 
      { 
       if(this.InternalCache.Count == 10) 
        this.InternalCache.RemoveLast(); 

       this.InternalCache.AddFirst(item); 
      } 
      finally 
      { 
       this.LockObj.ExitWriteLock(); 
      } 
     } 

     #region IEnumerable<T> Members 

     IEnumerator<T> IEnumerable<T>.GetEnumerator() 
     { 
      this.LockObj.EnterReadLock(); 
      try 
      { 
       foreach(T item in this.InternalCache) 
        yield return item; 
      } 
      finally 
      { 
       this.LockObj.ExitReadLock(); 
      } 
     } 

     #endregion 

     #region IEnumerable Members 

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
     { 
      this.LockObj.EnterReadLock(); 
      try 
      { 
       foreach (T item in this.InternalCache) 
        yield return item; 
      } 
      finally 
      { 
       this.LockObj.ExitReadLock(); 
      } 
     } 

     #endregion 
    } 

 
0

在发布此相同的答案了:Thread-safe cache libraries for .NET

我知道你的痛苦,因为我的Dedoose的建筑师之一。我已经搞砸了很多缓存库,并在经历了许多磨难之后最终创建了这个库。该缓存管理器的一个假设是,该类存储的所有集合都实现了一个接口,以便将Guid作为每个对象的“Id”属性。因为这是针对RIA的,所以它包含了很多用于从这些集合中添加/更新/删除项目的方法。

这里是我的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 colCachedItems) 
       { 
        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; 
} 

下面是我如何使用它的一个例子:

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

这里是有问题的接口:

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

} 

希望这有助于我我经历过几次地狱,终于到达这个解决方案,对我们来说,这是天赐之物,但我不能保证它是完美的,只是我们还没有发现问题。