mardi 1 avril 2008

Une partie de Cache - Cache / Partie 2

Et nous revoilà pour la seconde partie de ce post consacré au Cache tant décrié ;-) .

Afin de pouvoir stocker des éléments dans notre cache, nous allons devoir encapsuler notre objet Obj dans une classe générique (private au sein de la classe de Cache) qui contiendra la dernière date d'accès à Obj ainsi que sa durée de vie dans le cache. En voici le descriptif :

   1: private class CacheItem


   2: {


   3:     public V Item;


   4:     public DateTime LastRefresh;


   5:     public readonly bool SlidingExpiration;


   6:     public readonly TimeSpan Duration;


   7:  


   8:     public CacheItem(V value, bool slidingExpiration, TimeSpan duration)


   9:     {


  10:         Item = value;


  11:         LastRefresh = DateTime.Now;


  12:         SlidingExpiration = slidingExpiration;


  13:         Duration = duration;


  14:     }


  15: }




Si on se souvient du post précédent on peut voir rapidement que l'on reprend le type générique fourni dans la définition de notre cache (ie. Cache<K, V>).



On peut se demander pourquoi ne pas stocker directement la date d'expiration de l'objet plutôt que la date du dernier accès. Tout simplement parce que notre cache permet une SlidingExpiration (ie. prolonger la durée de vie de l'objet à chaque accès chaque fois que ce dernier est requêté). Cette SlidingExpiration imposerait de recalculer continuellement une nouvelle date d'expiration ce qui coûterait cher en terme de temps processeur.



Dans le post précédent, j'avais indiqué que notre cache allait implémenter l'interface IDictionnary. Or cette interface ne prévoit pas dans ses méthodes un argument définissant une durée de vie, ni la SlidingExpiration. Il faut donc pouvoir définir des valeurs par défaut lorsque ces méthodes seront appelées. Cela sera possible grâce aux propriétés suivante dans notre cache.





   1: public TimeSpan DefaultDuration


   2: {


   3:     get { return _defaultDuration; }


   4:     set { _defaultDuration = value; }


   5: }


   6:  


   7: public bool DefaultSlidingExpiration


   8: {


   9:     get { return _defaultSlidingExpiration; }


  10:     set { _defaultSlidingExpiration = value; }


  11: }




 



Mettre ici l'ensemble des méthodes que les différentes interfaces nous imposent d'implémenter nous prendrait un peu trop de place, et surtout je vais pas faire tout le boulot... :-) Je me contenterai seulement de mettre deux exemples (l'ajout et la récupération de valeur) qui illustre tout ce dont j'ai déjà parlé (le ReaderWriterLockSlim ainsi que notre classe générique) :





   1: public void Add(K key, V value)


   2: {


   3:     _lock.EnterWriteLock();


   4:     try


   5:     {


   6:         _items.Add(key, new CacheItem(value, _defaultSlidingExpiration, _defaultDuration));


   7:     }


   8:     finally


   9:     {


  10:         _lock.ExitWriteLock();


  11:     }


  12: }


  13:  


  14: ...


  15:  


  16: public bool TryGetValue(K key, out V value)


  17: {


  18:     _lock.EnterReadLock();


  19:     try


  20:     {


  21:         CacheItem ci;


  22:         if (_items.TryGetValue(key, out ci))


  23:         {


  24:             if (ci.SlidingExpiration)


  25:             {


  26:                 ci.LastRefresh = DateTime.Now;


  27:             }


  28:             value = ci.Item;


  29:             return true;


  30:         }


  31:     }


  32:     finally


  33:     {


  34:         _lock.ExitReadLock();


  35:     }


  36:     value = default(V);


  37:     return false;


  38: }




Et voilà, grâce à tout ça, et surtout si tu es motivé, tu devrais être capable d'avoir un cache qui se remplit sans problème mais qui est aussi et surtout entièrement Thread-Safe.



 



Dans un dernier post, nous verrons ensemble la touche finale de notre cache : comment faire expirer les éléments de notre cache.

Aucun commentaire: