using System; using System.Runtime.Caching; using System.Threading; namespace IStation { internal sealed partial class SharedMemoryCache { /// /// Set a cache item by key, function and optional eviction /// /// A unique identifier for the cache entry to insert /// A function to execute to get the data for the cache entry /// (Optional) An object that contains eviction details for the cache entry public void Set(string key, Func valueFunction, CacheItemPolicy policy = null) { if (key == null || valueFunction == null) { return; } Exception ex_func = null; this._cacheKeysBeingHandled.TryAdd(key, new CacheKeyBeingHandled()); lock (this._cacheKeysBeingHandled[key].SetOperation.CounterLock) { this._cacheKeysBeingHandled[key].SetOperation.Counter ++; } lock (this._cacheKeysBeingHandled[key].SetOperation.Lock) { if (this._cacheKeysBeingHandled[key].SetOperation.Thread != null) { try { this._cacheKeysBeingHandled[key].SetOperation.Thread.Abort(); } finally { this._cacheKeysBeingHandled[key].SetOperation.Thread = null; } } } lock (_cacheKeysBeingHandled[key].SetOperation.CounterLock) { if (this._cacheKeysBeingHandled[key].SetOperation.Counter > 1) { this._cacheKeysBeingHandled[key].SetOperation.Counter --; } } if (this._cacheKeysBeingHandled[key].SetOperation.Counter > 1) { return; // it's not the most recent thread, so ignore it } lock (_cacheKeysBeingHandled[key].SetOperation.Lock) { if (this._cacheKeysBeingHandled[key].SetOperation.Thread == null) { this._cacheKeysBeingHandled[key].SetOperation.Thread = new Thread(() => { try { object value = valueFunction(); ((IMemoryCacheDirect)this).Set(key, value, policy); } catch (ThreadAbortException) { } catch (Exception ex) { ex_func = ex; } finally { } }); this._cacheKeysBeingHandled[key].SetOperation.Thread.Start(); this._cacheKeysBeingHandled[key].SetOperation.Thread.Join(); } } if (ex_func != null) throw ex_func; } /// /// Set a cache item by key, value and optional eviction /// /// A unique identifier for the cache entry to insert /// The data for the cache entry /// (Optional) An object that contains eviction details for the cache item public void Set(string key, object value, CacheItemPolicy policy = null) { if (key == null) { return; } if (value != null) { ((IMemoryCacheDirect)this).Set(key, value, policy); // if there's currently a function opterating on this key if (this._cacheKeysBeingHandled.TryGetValue(key, out CacheKeyBeingHandled cacheKeyBeingHandled)) { cacheKeyBeingHandled.SetOperation.Thread?.Abort(); // if it wants the same type, then cancel it and give it this value if (cacheKeyBeingHandled.GetSetOperation.Type == value.GetType()) { cacheKeyBeingHandled.GetSetOperation.Value = value; cacheKeyBeingHandled.GetSetOperation.Thread.Abort(); } } } else { if (this._isWiping) { return; } this.Remove(key); } } /// /// The core method that directly sets a value in the wrapped memory cache /// /// key of cache item to set /// The data for the cache entry /// (Optional) An object that contains eviction details for the cache entry void IMemoryCacheDirect.Set(string key, object value, CacheItemPolicy policy) { var set = new Action(() => { this._isSetting = true; policy = policy ?? this.DefaultPolicy; try { if (policy != null) { this._memoryCache.Set(key, value, policy); } else { this._memoryCache[key] = value; } } finally { this._isSetting = false; } }); if (!this._isWiping) { set(); } else // hold the set until wiping is complete { lock (this._setLock) { SpinWait.SpinUntil(() => !this._isWiping); // one thread watching until wipe complete set(); } } } } }