Come cancellare MemoryCache?


100

Ho creato una cache utilizzando la classe MemoryCache. Aggiungo alcuni elementi ma quando ho bisogno di ricaricare la cache voglio prima cancellarla. Qual è il modo più veloce per farlo? Devo scorrere tutti gli elementi e rimuoverli uno alla volta o c'è un modo migliore?


1
Per .NET core controlla questa risposta.
Makla

Risposte:


61

Dispose il MemoryCache esistente e creare un nuovo oggetto MemoryCache.


3
Inizialmente ho usato MemoryCache.Default, causando Dispose a darmi qualche problema. Tuttavia, Dispose ha finito per essere la soluzione migliore che ho trovato. Grazie.
LaustN

11
@LaustN puoi approfondire il "dolore" causato da MemoryCache.Default? Attualmente sto utilizzando MemoryCache.Default ... La documentazione MemoryCache di MSDN mi fa chiedere se sia consigliabile eliminare e ricreare: "Non creare istanze di MemoryCache a meno che non sia richiesto. Se crei istanze di cache in applicazioni client e Web, le istanze di MemoryCache dovrebbero essere creato all'inizio del ciclo di vita dell'applicazione. " Questo si applica a .Default? Non sto dicendo che usare Dispose sia sbagliato, onestamente sto solo cercando chiarimenti su tutto questo.
ElonU Webdev

8
Ho pensato che era la pena ricordare che Dispose non invocare alcun CacheEntryRemovedCallbackattaccato alla elementi memorizzati nella cache in corso.
Mike Guthrie

8
@ElonU: la seguente risposta di Stack Overflow spiega alcuni dei problemi che potresti incontrare eliminando l'istanza predefinita: stackoverflow.com/a/8043556/216440 . Per citare: "Lo stato della cache è impostato per indicare che la cache è stata eliminata. Qualsiasi tentativo di chiamare metodi di memorizzazione nella cache pubblica che modificano lo stato della cache, come metodi che aggiungono, rimuovono o recuperano le voci della cache, potrebbe causare comportamento. Ad esempio, se chiami il metodo Set dopo che la cache è stata eliminata, si verifica un errore di non operazione. "
Simon Tewsi

56

Il problema con l'enumerazione

La sezione Note MemoryCache.GetEnumerator () avverte: "Il recupero di un enumeratore per un'istanza MemoryCache è un'operazione che richiede molte risorse e blocca. Pertanto, l'enumeratore non deve essere utilizzato nelle applicazioni di produzione."

Ecco perché , spiegato nello pseudocodice dell'implementazione GetEnumerator ():

Create a new Dictionary object (let's call it AllCache)
For Each per-processor segment in the cache (one Dictionary object per processor)
{
    Lock the segment/Dictionary (using lock construct)
    Iterate through the segment/Dictionary and add each name/value pair one-by-one
       to the AllCache Dictionary (using references to the original MemoryCacheKey
       and MemoryCacheEntry objects)
}
Create and return an enumerator on the AllCache Dictionary

Poiché l'implementazione divide la cache tra più oggetti Dictionary, deve riunire tutto in un'unica raccolta per restituire un enumeratore. Ogni chiamata a GetEnumerator esegue il processo di copia completo descritto sopra. Il dizionario appena creato contiene riferimenti alla chiave interna originale e agli oggetti valore, quindi i valori dei dati memorizzati nella cache effettivi non vengono duplicati.

L'avvertimento nella documentazione è corretto. Evita GetEnumerator (), incluse tutte le risposte precedenti che utilizzano query LINQ.

Una soluzione migliore e più flessibile

Ecco un modo efficiente per svuotare la cache che si basa semplicemente sull'infrastruttura di monitoraggio delle modifiche esistente. Fornisce inoltre la flessibilità di svuotare l'intera cache o solo un sottoinsieme denominato e non presenta nessuno dei problemi discussi sopra.

// By Thomas F. Abraham (http://www.tfabraham.com)
namespace CacheTest
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Caching;

    public class SignaledChangeEventArgs : EventArgs
    {
        public string Name { get; private set; }
        public SignaledChangeEventArgs(string name = null) { this.Name = name; }
    }

    /// <summary>
    /// Cache change monitor that allows an app to fire a change notification
    /// to all associated cache items.
    /// </summary>
    public class SignaledChangeMonitor : ChangeMonitor
    {
        // Shared across all SignaledChangeMonitors in the AppDomain
        private static event EventHandler<SignaledChangeEventArgs> Signaled;

        private string _name;
        private string _uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);

        public override string UniqueId
        {
            get { return _uniqueId; }
        }

        public SignaledChangeMonitor(string name = null)
        {
            _name = name;
            // Register instance with the shared event
            SignaledChangeMonitor.Signaled += OnSignalRaised;
            base.InitializationComplete();
        }

        public static void Signal(string name = null)
        {
            if (Signaled != null)
            {
                // Raise shared event to notify all subscribers
                Signaled(null, new SignaledChangeEventArgs(name));
            }
        }

        protected override void Dispose(bool disposing)
        {
            SignaledChangeMonitor.Signaled -= OnSignalRaised;
        }

        private void OnSignalRaised(object sender, SignaledChangeEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(e.Name) || string.Compare(e.Name, _name, true) == 0)
            {
                Debug.WriteLine(
                    _uniqueId + " notifying cache of change.", "SignaledChangeMonitor");
                // Cache objects are obligated to remove entry upon change notification.
                base.OnChanged(null);
            }
        }
    }

    public static class CacheTester
    {
        public static void TestCache()
        {
            MemoryCache cache = MemoryCache.Default;

            // Add data to cache
            for (int idx = 0; idx < 50; idx++)
            {
                cache.Add("Key" + idx.ToString(), "Value" + idx.ToString(), GetPolicy(idx));
            }

            // Flush cached items associated with "NamedData" change monitors
            SignaledChangeMonitor.Signal("NamedData");

            // Flush all cached items
            SignaledChangeMonitor.Signal();
        }

        private static CacheItemPolicy GetPolicy(int idx)
        {
            string name = (idx % 2 == 0) ? null : "NamedData";

            CacheItemPolicy cip = new CacheItemPolicy();
            cip.AbsoluteExpiration = System.DateTimeOffset.UtcNow.AddHours(1);
            cip.ChangeMonitors.Add(new SignaledChangeMonitor(name));
            return cip;
        }
    }
}

8
Sembra un'implementazione per la funzionalità Regione mancante.
Jowen

Molto bella. Ho provato a implementare qualcosa utilizzando monitor e guide di memorycache concatenati, ma stava iniziando a diventare un po 'brutto mentre cercavo di rafforzare la funzionalità.
Chao

7
Non consiglierei questo modello per uso generale. 1. È lento, non è colpa dell'implementazione, ma il metodo di smaltimento è estremamente lento. 2. Se gli elementi di espulsione dalla cache con una scadenza, viene comunque richiamato Change monitor. 3. La mia macchina stava assorbendo tutta la CPU e impiegava molto tempo per cancellare 30k elementi dalla cache durante l'esecuzione dei test delle prestazioni. Alcune volte, dopo aver atteso più di 5 minuti, ho terminato i test.
Aaron M

1
@PascalMathys Sfortunatamente, non esiste una soluzione migliore di questa. Ho finito per usarlo, nonostante gli svantaggi, poiché è ancora una soluzione migliore rispetto all'utilizzo dell'enumerazione.
Aaron M

9
@AaronM Questa soluzione è ancora migliore del semplice smaltimento della cache e istanziare una nuova?
RobSiklos

35

A partire dal http://connect.microsoft.com/VisualStudio/feedback/details/723620/memorycache-class-needs-a-clear-method

La soluzione è:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}

33
Dalla documentazione : il recupero di un enumeratore per un'istanza di MemoryCache è un'operazione che richiede molte risorse e blocca. Pertanto, l'enumeratore non deve essere utilizzato nelle applicazioni di produzione.
TrueWill

3
@emberdude È esattamente come recuperare un enumeratore: cosa pensi che faccia l'implementazione Select()?
RobSiklos

1
Personalmente, lo sto usando nella mia funzione di unit test [TestInitialize] per svuotare la cache di memoria per ogni unit test. In caso contrario, la cache persiste tra i test unitari fornendo risultati imprevisti quando si tenta di confrontare le prestazioni tra 2 funzioni.
Jacob Morrison

6
@JacobMorrison probabilmente, gli unit test non sono una "applicazione di produzione" :)
Mels

1
@Mels probabilmente, i test unitari dovrebbero essere scritti con gli stessi standard di "applicazione di produzione"! :)
Etherman


10

Se le prestazioni non sono un problema, questo simpatico one-liner farà il trucco:

cache.ToList().ForEach(a => cache.Remove(a.Key));


3

Potresti anche fare qualcosa del genere:


Dim _Qry = (From n In CacheObject.AsParallel()
           Select n).ToList()
For Each i In _Qry
    CacheObject.Remove(i.Key)
Next

3

Ran attraverso questo e, sulla base di esso, ha scritto un metodo chiaro e parallelo leggermente più efficace:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }

1
Lo hai testato per vedere se è più veloce (o più lento)?
Paul George,

1

Ero interessato solo a svuotare la cache e l'ho trovato come opzione, quando si utilizza c # GlobalCachingProvider

                var cache = GlobalCachingProvider.Instance.GetAllItems();
                if (dbOperation.SuccessLoadingAllCacheToDB(cache))
                {
                    cache.Clear();
                }

0

versione leggermente migliorata della risposta di magritte.

var cacheKeys = MemoryCache.Default.Where(kvp.Value is MyType).Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}

0

È possibile eliminare la cache MemoryCache.Default e quindi reimpostare il singleton del campo privato su null, in modo da ricreare MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.