/// <summary> /// Reset the timer on a specific cache item. If it still exists in cache, reset it. /// An exception is thrown when no item with the specify key is found. /// </summary> /// <param name="key">The unique key used to identify the item</param> /// <exception cref="System.Exception">Thrown when no item with the specified key is found</exception> public virtual void Reset(string key) { this.EvictExpiredItems(); Lazy <CacheItem> lazyItem; this.InternalCache.TryGetValue(key, out lazyItem); //try to reset this cache item's timer. if (lazyItem != null) { //A race condition could exist that would allow it to be in cache above, but then missing here. So eat any exception where that would happen try { var cacheItem = lazyItem.Value; lock (this.EvictionOrderList) { EvictionOrderList.Remove(cacheItem.EvictionDate, cacheItem); cacheItem.Reset(); EvictionOrderList.Add(cacheItem.EvictionDate, cacheItem); } } catch (Exception) { throw new Exception("No item with that key could be found"); } } else { throw new Exception("No item with that key could be found"); } }
/// <summary> /// Gets the value with specified the key. If no item with that key exists, /// the factory function is called to construct a new value. /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key">The unique key used to identify the item.</param> /// <param name="factory">A factory function that is called when the item is not in the /// cache, and a new copy of the item needs to be generated.</param> /// <param name="millisecondsToLive">The number of milliseconds that this item should remain in the cache. /// If null, the item will live in cache indefinitely . /// </param> /// <returns>The item from cache</returns> public virtual async Task <T> ResolveAsync <T>(string key, Func <Task <T> > factory, int?millisecondsToLive = null) { this.EvictExpiredItems(); var createdThisCall = false; millisecondsToLive = millisecondsToLive != null ? millisecondsToLive : this.DefaultMillisecondsToLive; var lazyCacheItem = this.InternalCache.GetOrAdd(key, (string k) => { //only allow a single thread to run this specific resolver function at a time. var lazyResult = new Lazy <CacheItem>(() => { createdThisCall = true; var lazyCacheItemValue = new Lazy <object>(() => { var factoryValue = factory(); return(factoryValue); }); var constructedCacheItem = new CacheItem(key, lazyCacheItemValue, millisecondsToLive); return(constructedCacheItem); }, LazyThreadSafetyMode.ExecutionAndPublication); return(lazyResult); }); CacheItem cacheItem = null; try { //get the cache item from lazy cacheItem = lazyCacheItem.Value; //force the cache item to run its lazy value factory var value = cacheItem.Value; //wait for the task to complete (which also propagates any exception) await(Task <T>) value; } catch (System.InvalidOperationException e) { this.Remove(key); throw new Exception("Possible recursive resolve() detected", e); } catch (ThreadAbortException e) { this.Remove(key); throw new Exception("Possible recursive resolve() detected", e); } catch (System.Exception e) { this.Remove(key); throw e; } finally { } //add this item to the eviction keys list lock (this.EvictionOrderList) { EvictionOrderList.Add(cacheItem.EvictionDate, cacheItem); } //if the item expired and was NOT created this call, toss it and get a new one. Will RARELY happen. if (cacheItem.IsExpired && createdThisCall == false) { this.Remove(key); return(await this.ResolveAsync(key, factory, millisecondsToLive)); } else { var task = (Task <T>)cacheItem.Value; var result = await task; //if this cache item should be discarded if (cacheItem != null && cacheItem.ShouldBeDiscarded) { //use the newest value from cache (if it exists) Lazy <CacheItem> lazyNewerCacheItem; this.InternalCache.TryGetValue(key, out lazyNewerCacheItem); if (lazyNewerCacheItem != null && lazyNewerCacheItem.IsValueCreated) { result = await(Task <T>) lazyNewerCacheItem.Value.Value; } else { //throw an exception because this item was discarded and no newer value exists throw new Exception($"Could not retrieve item with key '{key}' because it was removed before resolver function finished processing"); } } return(result); } }