2009-06-25 4 views
2

J'ai besoin de créer un module web, avec ce module j'ai besoin d'aller chercher le titre d'un site web, après je trouverai ce titre dont j'ai besoin dans le mécanisme de mise en cache thread sûr et je dois y enregistrer les 10 titres récupérés lat.Mécanisme de mise en cache sécurisé (pas de cache .NET intégré) ASPX C#

Une aide s'il vous plaît?

+0

Bien que j'ai un peu aimé l'orthographe de .NET (.NOT) je l'ai changé pour plus de clarté. –

+0

Qu'est-ce que vous demandez? Vous recherchez un cache thread-safe? –

Répondre

4

écrire du code de verrouillage serait assez facile, sauf pour ...

Comment voulez-vous récupérer? Voulez-vous être en mesure d'énumérer (foreach) sur la liste d'une manière thread-safe? Il y a plusieurs façons de faire cette partie, chacune avec des compromis.

Vous pourriez aller avec le comportement par défaut Cela ne fonctionnera probablement pas bien - vous obtiendrez une exception si quelqu'un change la liste pendant que vous l'énumérez.

Vous pouvez verrouiller la collection pendant toute la durée de l'énumération. Cela signifie que tout thread essayant d'ajouter à votre cache sera bloqué jusqu'à la fermeture de la boucle foreach.

Vous pouvez copier la collection en interne chaque fois que vous l'énumérez et enumerez la copie. Cela signifie que si quelqu'un ajoute à votre liste pendant que vous l'énumérez, vous ne verrez pas le changement.

Pour une liste de dix, j'irais avec la dernière option. (copie interne).

code Vous ressemblerait à quelque chose comme ceci:

 

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> EDIT 1: </B> Après avoir partagé quelques commentaires avec Rob Levine (ci-dessous), je pensais que je jetterais un autre couple alternatives là-bas.

Cette version vous permet d'itérer la collection sans verrou. Cependant, la méthode Add() est un peu plus chère, car elle doit copier la liste (déplacer la dépense hors de l'Enumerate, et sur l'ajouter).

 

    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> Edit 2: </B> Dans le dernier exemple, nous avons eu une itération vraiment bon marché, avec le compromis étant un appel plus cher à Ajouter(). Ensuite, j'ai pensé à utiliser un ReaderWriterLockSlim (il s'agit d'un objet .Net 3.5 - l'ancienne version de ReaderWriterLock offrait des performances médiocres)

Avec ce modèle, la méthode Add() est moins chère que le modèle précédent (bien que l'option Ajouter encore doit prendre un verrou exclusif). Avec ce modèle, nous n'avons pas besoin de créer des copies de la liste. Lorsque nous énumérons la liste, nous entrons dans un readlock, qui ne bloque pas les autres lecteurs, mais bloque/est bloqué par les writers (appels à Add). En ce qui concerne quel modèle est le meilleur - cela dépend probablement de la façon dont vous utilisez le cache. Je recommanderais de tester et de mesurer.

 

    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

Le problème avec cette implémentation est qu'il prend un verrou sur chaque appel à GetEnumerator, et n'est donc certainement pas optimal dans un environnement ASP.NET. Une meilleure solution serait de le changer afin qu'il n'y ait pas de verrou sur GetEnumerator(); Ont ajouter du travail sur une copie de LinkedList, et GetEnumerator ajouter à un autre (copie). Une fois l'ajout terminé, il doit mettre à jour la liste chaînée de GetEnumerator. De cette façon, add fonctionne toujours sur une copie séparée, et GetEnumerator ne nécessite pas de verrou. –

+0

@Rob Levine: L'implémentation pourrait devenir très intéressante - cette dernière partie: "Quand l'ajout se termine, il devrait mettre à jour la liste chaînée de GetEnumerator". Si quelqu'un énumère au moment où Add se termine, et que vous ne verrouillez pas, vous avez corrompu l'état. Maintenant, notez que pendant que l'implémentation ci-dessus se verrouille get enum (avec un convoi de verrouillage probable, sans argument), il copie la liste, de sorte que vous ne maintenez pas le verrou pendant la durée du for-each. – JMarsch

+0

@Rob Levine: J'y pensais un peu plus. Si, au lieu de modifier la liste que GetEnumerator lit, nous l'échangeons entièrement, nous pouvons optimiser les lectures et déplacer la dépense vers la méthode Add(). Peut-être que c'est ce que vous vouliez dire dans votre commentaire original. Si nous faisons cela, je pense que nous devons encore verrouiller la méthode Add, et nous déplaçons l'opération de copie à ajouter. Donc énumérer devient moins cher, et ajoute ramasse la dépense. Je posterai du code dans une autre réponse. – JMarsch

0

publiés cette même réponse à plus: Thread-safe cache libraries for .NET

Je sais que la douleur que je suis l'un des architectes de Dedoose. J'ai dérangé beaucoup de bibliothèques de mise en cache et j'ai fini par construire celui-ci après beaucoup de tribulations.La seule hypothèse pour ce gestionnaire de cache est que toutes les collections stockées par cette classe implémentent une interface pour obtenir un Guid en tant que propriété "Id" sur chaque objet. Étant donné que c'est pour un RIA, il comprend beaucoup de méthodes pour ajouter/mettre à jour/supprimer des éléments de ces collections.

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

Voici un exemple de la façon dont je l'utilise:

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

Voici l'interface en question:

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

} 

Hope this helps, je Nous avons traversé l'enfer et nous sommes revenus quelques fois pour finalement arriver à cela comme solution, et pour nous cela a été une aubaine, mais je ne peux pas garantir que c'est parfait, seulement que nous n'avons pas encore trouvé de problème.