c# - Cache class and modified collections -


i've written generic cache class designed return in-memory object , evaluate src (an iqueryable, or function returning iqueryable) occasionally. used in couple of places in app fetching lot of data via entity framework expensive.

it called

    //class level     private static cacheddata<foo> foocache= new cacheddata<foo>();       //method level     var results = foocache.getdata("foos", foorepo.include("bars")); 

although appeared work ok in testing, running on busy web server i'm seeing issues "collection modified; enumeration operation may not execute." errors in code consumes results.

this must because 1 thread overwriting results object inside lock, while using them, outside lock.

i'm guessing solution return copy of results each consumer rather original, , cannot allow copy occur while inside fetch lock, multiple copies occur simultaneously.

can suggest better way, or locking strategy please?

public class cacheddata<t> t:class  {     private static dictionary<string, ienumerable<t>> datacache { get; set; }      public  static dictionary<string, datetime> expire { get; set; }     public int ttl { get; set; }     private object lo = new object();      public cacheddata()     {         ttl = 600;         expire = new dictionary<string, datetime>();         datacache = new dictionary<string, ienumerable<t>>();     }      public ienumerable<t> getdata(string key, func<iqueryable<t>> src)     {         var bc = brandkey(key);         if (!datacache.containskey(bc)) fetch(bc, src);         if (datetime.now > expire[bc]) fetch(bc, src);         return datacache[bc];     }       public ienumerable<t> getdata(string key, iqueryable<t> src)     {         var bc = brandkey(key);         if ((!datacache.containskey(bc)) || (datetime.now > expire[bc])) fetch(bc, src);         return datacache[bc];     }      private void fetch(string key, iqueryable<t> src )     {         lock (lo)         {             if ((!datacache.containskey(key)) || (datetime.now > expire[key])) executefetch(key, src);         }     }      private void fetch(string key, func<iqueryable<t>> src)     {         lock (lo)         {             if ((!datacache.containskey(key)) || (datetime.now > expire[key])) executefetch(key, src());         }     }      private void executefetch(string key, iqueryable<t> src)     {         if (!datacache.containskey(key)) datacache.add(key, src.tolist());         else datacache[key] = src.tolist();         if (!expire.containskey(key)) expire.add(key, datetime.now.addseconds(ttl));         else expire[key] = datetime.now.addseconds(ttl);     }       private string brandkey(string key, int? brandid = null)     {         return string.format("{0}/{1}", brandid ?? config.brandid, key);     }  } 

i use concurrentdictionary<tkey, lazy<tvalue>>. gives lock per key. makes strategy hold lock while fetching viable. avoids cache-stampeding. guarantees 1 evaluation per key ever happen. lazy<t> automates locking entirely.

regarding expiration logic: set timer cleans dictionary (or rewrites entirely) every x seconds.


Comments