Jak wyczyścić MemoryCache?

100

Utworzyłem pamięć podręczną przy użyciu klasy MemoryCache. Dodaje do niego kilka pozycji, ale kiedy muszę ponownie załadować pamięć podręczną, chcę ją najpierw wyczyścić. Jaki jest najszybszy sposób na zrobienie tego? Czy powinienem przejrzeć wszystkie elementy i usuwać je pojedynczo, czy jest lepszy sposób?

Retrocoder
źródło
1
W przypadku .NET core sprawdź odpowiedź.
Makla

Odpowiedzi:

61

Dispose istniejący MemoryCache i utwórz nowy obiekt MemoryCache.

GvS
źródło
3
Początkowo użyłem MemoryCache.Default, powodując, że Dispose przysporzył mi trochę żalu. Mimo to, Dispose okazał się najlepszym rozwiązaniem, jakie mogłem znaleźć. Dzięki.
LaustN
11
@LaustN Czy możesz rozwinąć „smutek” spowodowany przez MemoryCache.Default? Obecnie używam MemoryCache.Default ... Dokumentacja MSDN MemoryCache sprawia, że ​​zastanawiam się, czy usuwanie i ponowne tworzenie jest zalecane: „Nie twórz wystąpień MemoryCache, chyba że jest to wymagane. Jeśli tworzysz wystąpienia pamięci podręcznej w aplikacjach klienckich i internetowych, wystąpienia MemoryCache powinny być tworzone na wczesnym etapie cyklu życia aplikacji. ” Czy to dotyczy .Default? Nie mówię, że używanie Dispose jest złe, szczerze mówiąc, po prostu szukam wyjaśnień na ten temat.
ElonU Webdev,
8
Myślałem, że to warto wspomnieć, że Dispose nie stosował żadnych CacheEntryRemovedCallbackprzypisane do bieżącej pozycji buforowane.
Mike Guthrie
8
@ElonU: Poniższa odpowiedź Stack Overflow wyjaśnia niektóre zmartwienia, jakie możesz napotkać przy usuwaniu domyślnej instancji: stackoverflow.com/a/8043556/216440 . Cytując: „Stan pamięci podręcznej jest ustawiony tak, aby wskazywać, że pamięć podręczna została usunięta. Każda próba wywołania publicznych metod pamięci podręcznej, które zmieniają stan pamięci podręcznej, takich jak metody dodające, usuwające lub pobierające pozycje pamięci podręcznej, mogą powodować nieoczekiwane Na przykład, jeśli wywołasz metodę Set po usunięciu pamięci podręcznej, wystąpi błąd braku operacji. "
Simon Tewsi
56

Problem z wyliczeniem

Sekcja Remarks MemoryCache.GetEnumerator () ostrzega: „Pobieranie modułu wyliczającego dla wystąpienia MemoryCache jest operacją intensywnie wykorzystującą zasoby i blokującą. Dlatego moduł wyliczający nie powinien być używany w aplikacjach produkcyjnych”.

Oto dlaczego , wyjaśnione w pseudokodzie implementacji 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

Ponieważ implementacja dzieli pamięć podręczną na wiele obiektów Dictionary, musi połączyć wszystko w jedną kolekcję, aby zwrócić moduł wyliczający. Każde wywołanie GetEnumerator wykonuje pełny proces kopiowania opisany powyżej. Nowo utworzony słownik zawiera odniesienia do oryginalnego klucza wewnętrznego i obiektów wartości, więc rzeczywiste wartości danych w pamięci podręcznej nie są duplikowane.

Ostrzeżenie w dokumentacji jest prawidłowe. Unikaj GetEnumerator () - w tym wszystkie powyższe odpowiedzi, które używają zapytań LINQ.

Lepsze i bardziej elastyczne rozwiązanie

Oto skuteczny sposób czyszczenia pamięci podręcznej, który po prostu opiera się na istniejącej infrastrukturze monitorowania zmian. Zapewnia również elastyczność czyszczenia całej pamięci podręcznej lub tylko nazwanego podzbioru i nie powoduje żadnego z omówionych powyżej problemów.

// 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
źródło
8
Wydaje się, że implementacja brakującej funkcji regionu.
Jowen
Bardzo dobrze. Próbowałem zaimplementować coś za pomocą łańcuchowych monitorów pamięci podręcznej i przewodników, ale zaczęło robić się trochę brzydko, gdy próbowałem zaostrzyć funkcjonalność.
Chao,
7
Nie polecam tego wzoru do ogólnego użytku. 1. Jego powolny, bez winy implementacji, ale metoda usuwania jest bardzo powolna. 2. Jeśli eksmitujesz elementy z pamięci podręcznej po wygaśnięciu, nadal zostanie wywołana zmiana monitora. 3. Mój komputer połykał cały procesor i wyczyszczenie 30 000 pozycji z pamięci podręcznej zajmowało naprawdę dużo czasu, gdy przeprowadzałem testy wydajności. Kilka razy po odczekaniu ponad 5 minut właśnie skończyłem testy.
Aaron M
1
@PascalMathys Niestety nie ma lepszego rozwiązania niż to. Skończyło się na tym, że korzystałem z niego pomimo wad, ponieważ jest to wciąż lepsze rozwiązanie niż wyliczenie.
Aaron M
9
@AaronM Czy to rozwiązanie jest nadal lepsze niż zwykłe pozbycie się pamięci podręcznej i utworzenie nowego?
RobSiklos
35

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

Sposób obejścia:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
magritte
źródło
33
Z dokumentacji : pobieranie
TrueWill
3
@emberdude To dokładnie to samo, co pobieranie modułu wyliczającego - co według ciebie robi implementacja Select()?
RobSiklos
1
Osobiście używam tego w mojej funkcji testu jednostkowego [TestInitialize], aby wyczyścić pamięć podręczną dla każdego testu jednostkowego. W przeciwnym razie pamięć podręczna utrzymuje się w testach jednostkowych, dając niezamierzone wyniki podczas próby porównania wydajności między dwiema funkcjami.
Jacob Morrison
6
@JacobMorrison prawdopodobnie testy jednostkowe nie są „aplikacją produkcyjną” :)
Mels
1
@Mels prawdopodobnie testy jednostkowe powinny być napisane zgodnie z tymi samymi standardami, co „aplikacja produkcyjna”! :)
Etherman
21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}
Roger Far
źródło
3
Ma to takie samo ryzyko jak odpowiedź @ Tony'ego; proszę zobaczyć mój komentarz pod tym.
TrueWill
@TrueWill Kto jest lub był @Tony?
Alex Angas
2
@AlexAngas - Mógł zmienić nazwisko na magritte. Zobacz także stackoverflow.com/questions/4183270/…
TrueWill
10

Jeśli wydajność nie jest problemem, ten fajny, jednoliniowy tekst załatwi sprawę:

cache.ToList().ForEach(a => cache.Remove(a.Key));
user425678
źródło
3

Możesz też zrobić coś takiego:


Dim _Qry = (From n In CacheObject.AsParallel()
           Select n).ToList()
For Each i In _Qry
    CacheObject.Remove(i.Key)
Next
Kevin
źródło
3

Przebiegłem przez to i na tej podstawie napisałem nieco bardziej efektywną, równoległą przejrzystą metodę:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }
Pedro G. Dias
źródło
1
Czy przetestowałeś to, aby sprawdzić, czy jest szybsze (lub wolniejsze)?
Paul George
1

Byłem zainteresowany tylko wyczyszczeniem pamięci podręcznej i znalazłem to jako opcję, używając c # GlobalCachingProvider

                var cache = GlobalCachingProvider.Instance.GetAllItems();
                if (dbOperation.SuccessLoadingAllCacheToDB(cache))
                {
                    cache.Clear();
                }
Brian
źródło
0

nieco ulepszona wersja odpowiedzi magritte.

var cacheKeys = MemoryCache.Default.Where(kvp.Value is MyType).Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
Khachatur
źródło
0

Możesz usunąć pamięć podręczną MemoryCache.Default, a następnie ponownie ustawić singleton pola prywatnego na null, aby ponownie utworzył MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
drzwi 99
źródło