Wie lösche ich MemoryCache?

100

Ich habe einen Cache mit der MemoryCache-Klasse erstellt. Ich füge einige Elemente hinzu, aber wenn ich den Cache neu laden muss, möchte ich ihn zuerst löschen. Was ist der schnellste Weg, dies zu tun? Sollte ich alle Elemente durchlaufen und einzeln entfernen, oder gibt es einen besseren Weg?

Retrocoder
quelle
1
Überprüfen Sie für .NET Core diese Antwort.
Makla

Antworten:

61

Dispose den vorhandenen MemoryCache und erstellen Sie ein neues MemoryCache-Objekt.

GvS
quelle
3
Ich habe anfangs MemoryCache.Default verwendet, was dazu führte, dass Dispose mir etwas Kummer bereitete. Trotzdem war Dispose die beste Lösung, die ich finden konnte. Vielen Dank.
LaustN
11
@LaustN können Sie die "Trauer", die durch MemoryCache.Default verursacht wird, näher erläutern? Ich verwende derzeit MemoryCache.Default ... In der MemoryCache-Dokumentation von MSDN frage ich mich, ob das Entsorgen und Neuerstellen empfohlen wird: "Erstellen Sie keine MemoryCache-Instanzen, es sei denn, dies ist erforderlich. Wenn Sie Cache-Instanzen in Client- und Webanwendungen erstellen, sollten die MemoryCache-Instanzen dies tun." früh im Anwendungslebenszyklus erstellt werden. " Gilt das für .Default? Ich sage nicht, dass die Verwendung von Dispose falsch ist, ich suche ehrlich gesagt nur nach einer Klärung all dessen.
ElonU Webdev
8
Ich dachte , es war erwähnenswert , dass Dispose tut jeder invoke CacheEntryRemovedCallbackden aktuellen Cache gespeicherten Elemente angebracht.
Mike Guthrie
8
@ElonU: Die folgende Antwort zum Stapelüberlauf erklärt einige der Probleme, die beim Entsorgen der Standardinstanz auftreten können: stackoverflow.com/a/8043556/216440 . Um es zu zitieren: "Der Status des Caches wird festgelegt, um anzuzeigen, dass der Cache entsorgt ist. Jeder Versuch, öffentliche Caching-Methoden aufzurufen, die den Status des Caches ändern, z. B. Methoden zum Hinzufügen, Entfernen oder Abrufen von Cache-Einträgen, kann zu unerwarteten Ereignissen führen Verhalten. Wenn Sie beispielsweise die Set-Methode aufrufen, nachdem der Cache entsorgt wurde, tritt ein No-Op-Fehler auf. "
Simon Tewsi
56

Das Problem mit der Aufzählung

Der Abschnitt MemoryCache.GetEnumerator () Remarks warnt: "Das Abrufen eines Enumerators für eine MemoryCache-Instanz ist eine ressourcenintensive und blockierende Operation. Daher sollte der Enumerator nicht in Produktionsanwendungen verwendet werden."

Im Pseudocode der GetEnumerator () -Implementierung wird der folgende Grund erläutert:

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

Da die Implementierung den Cache auf mehrere Dictionary-Objekte aufteilt, muss alles in einer einzigen Sammlung zusammengefasst werden, um einen Enumerator zurückzugeben. Jeder Aufruf von GetEnumerator führt den oben beschriebenen vollständigen Kopiervorgang aus. Das neu erstellte Wörterbuch enthält Verweise auf die ursprünglichen internen Schlüssel- und Wertobjekte, sodass Ihre tatsächlich zwischengespeicherten Datenwerte nicht dupliziert werden.

Die Warnung in der Dokumentation ist korrekt. Vermeiden Sie GetEnumerator () - einschließlich aller obigen Antworten, die LINQ-Abfragen verwenden.

Eine bessere und flexiblere Lösung

Hier ist eine effiziente Methode zum Löschen des Caches, die einfach auf der vorhandenen Infrastruktur zur Änderungsüberwachung aufbaut. Es bietet auch die Flexibilität, entweder den gesamten Cache oder nur eine benannte Teilmenge zu löschen, und weist keines der oben diskutierten Probleme auf.

// 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;
        }
    }
}
Thomas F. Abraham
quelle
8
Scheint eine Implementierung für die fehlende Regionsfunktionalität zu sein.
Jowen
Sehr schön. Ich habe versucht, etwas mit verketteten Memorycache-Monitoren und -Guids zu implementieren, aber es wurde etwas hässlich, als ich versuchte, die Funktionalität zu verbessern.
Chao
7
Ich würde dieses Muster nicht für den allgemeinen Gebrauch empfehlen. 1. Es ist langsam, kein Fehler der Implementierung, aber die Entsorgungsmethode ist extrem langsam. 2. Wenn Sie Elemente mit einem Ablauf aus dem Cache entfernen, wird der Änderungsmonitor weiterhin aufgerufen. 3. Mein Computer hat die gesamte CPU verschluckt und es hat sehr lange gedauert, 30.000 Elemente aus dem Cache zu löschen, als ich Leistungstests durchführte. Ein paar Mal, nachdem ich mehr als 5 Minuten gewartet hatte, habe ich gerade die Tests beendet.
Aaron M
1
@PascalMathys Leider gibt es keine bessere Lösung als diese. Ich habe es trotz der Nachteile verwendet, da es immer noch eine bessere Lösung ist als die Aufzählung.
Aaron M
9
@AaronM Ist diese Lösung immer noch besser als nur den Cache zu entsorgen und einen neuen zu instanziieren?
RobSiklos
35

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

Die Problemumgehung lautet:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
magritte
quelle
33
Aus der Dokumentation : Das Abrufen eines Enumerators für eine MemoryCache-Instanz ist eine ressourcenintensive und blockierende Operation. Daher sollte der Enumerator nicht in Produktionsanwendungen verwendet werden.
TrueWill
3
@emberdude Es ist genau das gleiche wie das Abrufen eines Enumerators - was macht die Implementierung Ihrer Select()Meinung nach?
RobSiklos
1
Persönlich verwende ich dies in meiner Unit-Test-Funktion [TestInitialize], um den Speicher-Cache für jeden Unit-Test zu löschen. Andernfalls bleibt der Cache über Unit-Tests hinweg erhalten und liefert unbeabsichtigte Ergebnisse, wenn versucht wird, die Leistung zwischen zwei Funktionen zu vergleichen.
Jacob Morrison
6
@ JacobMorrison wohl, Unit-Tests sind keine "Produktionsanwendung" :)
Mels
1
@ Mel wohl, Unit-Tests sollten nach den gleichen Standards wie "Produktionsanwendung" geschrieben werden! :)
Etherman
21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}
Roger Far
quelle
3
Dies hat das gleiche Risiko wie die Antwort von @ Tony. Bitte beachten Sie meinen Kommentar darunter.
TrueWill
@TrueWill Wer ist oder war @Tony?
Alex Angas
2
@AlexAngas - Möglicherweise hat er seinen Namen in magritte geändert. Siehe auch stackoverflow.com/questions/4183270/…
TrueWill
10

Wenn Leistung kein Problem ist, wird dieser nette Einzeiler den Trick machen:

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

Sie könnten auch so etwas tun:


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

Ich bin darauf gestoßen und habe darauf basierend eine etwas effektivere, parallele klare Methode geschrieben:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }
Pedro G. Dias
quelle
1
Haben Sie es getestet, um festzustellen, ob es schneller (oder langsamer) ist?
Paul George
1

Ich war nur daran interessiert, den Cache zu leeren, und fand dies als Option, wenn ich den c # GlobalCachingProvider verwendete

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

eine etwas verbesserte version von magritte antwort.

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

Sie können den MemoryCache.Default-Cache entsorgen und dann den Singleton für das private Feld auf null zurücksetzen, damit der MemoryCache.Default neu erstellt wird.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
Türen99
quelle