MemoryCache AbsoluteExpiration si comporta in modo strano

Sto cercando di utilizzare un MemoryCache in .net 4.5 per tenere traccia di e aggiornare automaticamente vari elementi, ma sembra che non importa quello che ho impostato come AbsoluteExpiration che scadrà sempre solo in 15 secondi o più.

Voglio che gli elementi della cache scadano ogni 5 secondi, ma scade sempre in almeno 15 secondi, e se sposto il tempo di scadenza, finirà per essere qualcosa come 15 secondi + il mio intervallo di aggiornamento, ma mai meno di 15 secondi .

C’è qualche risoluzione del timer interna che non vedo? Ho esaminato un po ‘del codice riflesso di System.Runtime.Caching.MemoryCache e nulla mi ha colpito, e non sono stato in grado di trovare nessun altro che abbia questo problema su Internet.

Ho un esempio molto semplice qui sotto che illustra il problema.

Quello che voglio è che CacheEntryUpdate venga colpito ogni 5 secondi circa e si aggiorni con i nuovi dati, ma, come ho detto, viene colpito solo in 15+ secondi.

 static MemoryCache MemCache; static int RefreshInterval = 5000; protected void Page_Load(object sender, EventArgs e) { if (MemCache == null) MemCache = new MemoryCache("MemCache"); if (!MemCache.Contains("cacheItem")) { var cacheObj = new object(); var policy = new CacheItemPolicy { UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate), AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval) }; var cacheItem = new CacheItem("cacheItem", cacheObj); MemCache.Set("cacheItem", cacheItem, policy); } } private void CacheEntryUpdate(CacheEntryUpdateArguments args) { var cacheItem = MemCache.GetCacheItem(args.Key); var cacheObj = cacheItem.Value; cacheItem.Value = cacheObj; args.UpdatedCacheItem = cacheItem; var policy = new CacheItemPolicy { UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate), AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval) }; args.UpdatedCacheItemPolicy = policy; } 

L’ho capito. C’è un timeSpan internal static readonly TimeSpan su System.Runtime.Caching.CacheExpires chiamato _tsPerBucket che è hardcoded a 20 secondi.

Apparentemente, questo campo è ciò che viene utilizzato nei timer interni che vengono eseguiti e controllano se gli elementi della cache sono scaduti.

Sto lavorando su questo sovrascrivendo il valore utilizzando la reflection e cancellando l’istanza di MemoryCache predefinita per resettare tutto. Sembra funzionare, anche se è un hack gigante.

Ecco il codice aggiornato:

 static MemoryCache MemCache; static int RefreshInterval = 1000; protected void Page_Load(object sender, EventArgs e) { if (MemCache == null) { const string assembly = "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; var type = Type.GetType("System.Runtime.Caching.CacheExpires, " + assembly, true, true); var field = type.GetField("_tsPerBucket", BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null, TimeSpan.FromSeconds(1)); type = typeof(MemoryCache); field = type.GetField("s_defaultCache", BindingFlags.Static | BindingFlags.NonPublic); field.SetValue(null, null); MemCache = new MemoryCache("MemCache"); } if (!MemCache.Contains("cacheItem")) { var cacheObj = new object(); var policy = new CacheItemPolicy { UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate), AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval) }; var cacheItem = new CacheItem("cacheItem", cacheObj); MemCache.Set("cacheItem", cacheItem, policy); } } private void CacheEntryUpdate(CacheEntryUpdateArguments args) { var cacheItem = MemCache.GetCacheItem(args.Key); var cacheObj = cacheItem.Value; cacheItem.Value = cacheObj; args.UpdatedCacheItem = cacheItem; var policy = new CacheItemPolicy { UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate), AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval) }; args.UpdatedCacheItemPolicy = policy; } 

Per MatteoSp – il pollingInterval nella configurazione o il NameValueCollection nel costruttore è un timer diverso. È un intervallo che quando chiamato utilizzerà le altre due proprietà di configurazione per determinare se la memoria è a un livello che richiede che le voci vengano rimosse usando il metodo Trim.

Sareste disposti / in grado di passare dal precedente System.Runtime.Caching al nuovo Microsft.Extensions.Caching ? la versione 1.x supporta netstandard 1.3 e net451. Se è così allora l’API migliorata sosterrà l’uso che descrivi senza trucco con la riflessione.

L’object MemoryCacheOptions ha una proprietà ExpirationScanFrequency che consente di controllare la frequenza di scansione della pulizia della cache, vedere https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions.expirationscanfrequency ? view = aspnetcore-2.0

Si noti che non esiste più scadenza basata sui timer (questa è una decisione sulla progettazione delle prestazioni) e quindi ora la pressione della memoria o la chiamata a uno dei metodi basati su Get () per gli elementi memorizzati nella cache sono ora i trigger per la scadenza. Tuttavia, è ansible forzare la scadenza basata sul tempo utilizzando i token di cancellazione, vedere questa risposta SO per un esempio https://stackoverflow.com/a/47949111/3140853 .

Una versione aggiornata basata sulla risposta di @ Jared. Insread di modificare l’istanza di MemoryCache predefinita, qui ne crea una nuova.

 class FastExpiringCache { public static MemoryCache Default { get; } = Create(); private static MemoryCache Create() { MemoryCache instance = null; Assembly assembly = typeof(CacheItemPolicy).Assembly; Type type = assembly.GetType("System.Runtime.Caching.CacheExpires"); if( type != null) { FieldInfo field = type.GetField("_tsPerBucket", BindingFlags.Static | BindingFlags.NonPublic); if(field != null && field.FieldType == typeof(TimeSpan)) { TimeSpan originalValue = (TimeSpan)field.GetValue(null); field.SetValue(null, TimeSpan.FromSeconds(3)); instance = new MemoryCache("FastExpiringCache"); field.SetValue(null, originalValue); // reset to original value } } return instance ?? new MemoryCache("FastExpiringCache"); } }