0

Je développe une classe de thread thread que je vais utiliser comme cache, cela devrait fonctionner dans .NET et Mono. Les éléments ont une durée de vie et chaque fois qu'un objet est récupéré, son temps de vie est actualisé. Chaque fois que j'ajoute un élément, l'horodatage est ajouté à une autre collection qui contient les mêmes clés. Une minuterie soulève la méthode qui recherche les éléments périmés et les supprime. Lorsque j'essaie d'obtenir et d'élément, je dois également fournir un délégué indiquant comment l'obtenir s'il n'existe pas dans le cache. J'ai des tests, et bien que le retrait des éléments devrait se produire toutes les 30 secondes dans le test, cela arrive très souvent, presque toutes les secondes, et je ne sais pas pourquoi.Class pour mantain un cache de thread sûr

C'est la classe:

public class GenericCache<TId, TItem>:IDisposable where TItem : class 
{ 
    SortedDictionary<TId, TItem> _cache; 
    SortedDictionary<TId, DateTime> _timeouts; 
    Timer _timer; 
    Int32 _cacheTimeout; 
    System.Threading.ReaderWriterLockSlim _locker; 

    public GenericCache(Int32 minutesTTL) 
    { 
     _locker = new System.Threading.ReaderWriterLockSlim(); 
     _cacheTimeout = minutesTTL; 
     _cache = new SortedDictionary<TId, TItem>(); 
     _timeouts = new SortedDictionary<TId, DateTime>(); 
     _timer = new Timer((minutesTTL * 60)/2); 
     _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed); 
     _timer.AutoReset = true; 
     _timer.Enabled = true; 
     _timer.Start(); 
    } 

    /// <summary> 
    /// Get an item, if it doesn't exist, create it using the delegate 
    /// </summary> 
    /// <param name="id">Id for the item</param> 
    /// <param name="create">A delegate that generates the item</param> 
    /// <returns>The item</returns> 
    public TItem Get(TId id, Func<TItem> create) 
    { 
     _locker.EnterUpgradeableReadLock(); 
     try 
     { 
      TItem item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault(); 
      if (item == null) 
      { 
       _locker.EnterWriteLock(); 
       // check again, maybe another thread is waiting in EnterWriteLock cos the same item is null 
       item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault(); 
       if (item == null) 
       { 
        Debug.Write("_"); 
        item = create.Invoke(); 
        if (item != null) 
        { 
         _cache.Add(id, item); 
         _timeouts.Add(id, DateTime.Now); 
        } 
       } 
      } 
      else 
       _timeouts[id] = DateTime.Now; 

      return item; 
     } 
     finally 
     { 
      if(_locker.IsWriteLockHeld) 
       _locker.ExitWriteLock(); 
      _locker.ExitUpgradeableReadLock(); 
     } 
    } 

    /// <summary> 
    /// Execute a delegate in the items, for example clear nested collections. 
    /// </summary> 
    /// <param name="action">The delegate</param> 
    public void ExecuteOnItems(Action<TItem> action) 
    { 
     _locker.EnterWriteLock(); 
     try 
     { 
      foreach (var i in _cache.Values) 
       action.Invoke(i); 
     } 
     finally 
     { 
      _locker.ExitWriteLock(); 
     } 
    } 

    /// <summary> 
    /// Clear this cache 
    /// </summary> 
    public void Clear() 
    { 
     _locker.EnterWriteLock(); 
     try 
     { 
      _cache.Clear(); 
      _timeouts.Clear(); 
     } 
     finally 
     { 
      _locker.ExitWriteLock(); 
     } 
    } 

    /// <summary> 
    /// Remove outdated items 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    private void _timer_Elapsed(object sender, ElapsedEventArgs e) 
    { 
     _locker.EnterUpgradeableReadLock(); 
     try 
     { 
      var delete = _timeouts.Where(to => DateTime.Now.Subtract(to.Value).TotalMinutes > _cacheTimeout).ToArray(); 

      if(delete.Any()) 
      { 
       _locker.EnterWriteLock(); 
       foreach (var timeitem in delete) 
       { 
        Debug.Write("-"); 
        _cache.Remove(timeitem.Key); 
        _timeouts.Remove(timeitem.Key); 
       } 
      } 
     } 
     finally 
     { 
      if(_locker.IsWriteLockHeld) 
       _locker.ExitWriteLock(); 
      _locker.ExitUpgradeableReadLock(); 
     } 
    } 

    #region IDisposable Members 
    private volatile Boolean disposed = false; 
    protected virtual void Dispose(bool disposing) 
    { 
     if (!disposed) 
     { 
      if (disposing) 
      try 
      { 
       this.Clear(); 
      } 
      finally 
      { 
       _locker.Dispose(); 
      } 

      disposed = true; 
     } 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    ~GenericCache() 
    { 
     Dispose(false); 
    } 

    #endregion 
} 

Comme vous pouvez le voir, en mode débogage, lorsqu'un élément est ajouté un symbole « _ » est imprimé, et quand et élément est supprimé un symbole « - » est imprimé. Dans les tests, après la deuxième minute peut voir comment les éléments sont supprimés et ajoutés dans la même seconde, lorsque les éléments doivent être supprimés seulement toutes les 30 secondes, et je ne sais pas pourquoi:

Voici comment je teste:

 static void Main(string[] args) 
    { 
     GenericCache<Int32, String> cache = new GenericCache<Int32, String>(1); 

     Debug.Listeners.Add(new ConsoleTraceListener()); 

     Action a = delegate() 
     { 
      Random r = new Random(DateTime.Now.Millisecond); 
      while (true) 
      { 
       Int32 number = r.Next(0, 9999); 
       if (String.IsNullOrEmpty(cache.Get(number,() => number.ToString()))) 
        Debug.Write("E"); 
       Thread.Sleep(number); 
      } 
     }; 

     for (int i = 0; i < 150; i++) 
     { 
      new Thread(new ThreadStart(a)).Start(); 
      Thread.Sleep(5); 
     } 

     Console.ReadKey(); 
    } 

Voyez-vous des problèmes dans la classe GenericCache?

Merci d'avance, cordialement.

+2

pourquoi réinventer la roue? Quel est le problème avec EL Caching? – Nix

+3

Pourquoi ne pas utiliser le cache de bibliothèque d'entreprise? C'est threadsafe. http://msdn.microsoft.com/en-us/library/cc309103.aspx – Randolpho

+1

@Nix: heh ... :) – Randolpho

Répondre

1

Premier problème que je vois (en supposant que vous utilisez System.Timers.Timer accepte les millisecondes et vous passez des secondes).

_timer = new Timer((minutesTTL * 60000)/2); 
+0

1 * 60000 est la milliseconde qu'une minute contient, n'est-ce pas? Je règle la minuterie sur la moitié du temps de vie. – vtortola

+1

droite, vous pourriez diviser cela par ... je me demandais juste pourquoi vous feriez cela? – Nix

+0

bien ... peut-être n'est pas la bonne approche: P Je voulais juste faire en sorte qu'un élément désuet reste dans la collection aussi minimun que possible. – vtortola

Questions connexes