Comment effacer MemoryCache?

100

J'ai créé un cache en utilisant la classe MemoryCache. J'y ajoute quelques éléments, mais lorsque j'ai besoin de recharger le cache, je veux d'abord l'effacer. Quelle est la manière la plus rapide de procéder? Dois-je parcourir tous les éléments et les supprimer un par un ou y a-t-il un meilleur moyen?

Retrocodeur
la source
1
Pour .NET core, vérifiez cette réponse.
Makla

Réponses:

61

Dispose le MemoryCache existant et créez un nouvel objet MemoryCache.

GvS
la source
3
J'ai d'abord utilisé MemoryCache.Default, ce qui a causé du chagrin à Dispose. Pourtant, Dispose a fini par être la meilleure solution que j'ai pu trouver. Merci.
LaustN
11
@LaustN pouvez-vous élaborer sur le «chagrin» causé par MemoryCache.Default? J'utilise actuellement MemoryCache.Default. être créé tôt dans le cycle de vie de l'application. " Cela s'applique-t-il à .Default? Je ne dis pas que l'utilisation de Dispose est une erreur, je cherche honnêtement juste des éclaircissements sur tout cela.
ElonU Webdev
8
J'ai pensé qu'il valait la peine de mentionner que Dispose cela invoque tout CacheEntryRemovedCallbackattaché aux éléments actuels en cache.
Mike Guthrie
8
@ElonU: La réponse suivante Stack Overflow explique certains des problèmes que vous pouvez rencontrer lors de la suppression de l'instance par défaut: stackoverflow.com/a/8043556/216440 . Pour citer: «L’état du cache est défini pour indiquer que le cache est supprimé. Toute tentative d’appel de méthodes de mise en cache publique qui modifient l’état du cache, telles que les méthodes qui ajoutent, suppriment ou récupèrent des entrées de cache, peut provoquer des comportement. Par exemple, si vous appelez la méthode Set après la suppression du cache, une erreur No-op se produit. "
Simon Tewsi
56

Le problème de l'énumération

La section Notes de MemoryCache.GetEnumerator () avertit: "La récupération d'un énumérateur pour une instance de MemoryCache est une opération de blocage et gourmande en ressources. Par conséquent, l'énumérateur ne doit pas être utilisé dans les applications de production."

Voici pourquoi , expliqué dans le pseudocode de l'implémentation 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

Étant donné que l'implémentation divise le cache entre plusieurs objets Dictionary, elle doit tout rassembler dans une seule collection afin de rendre un énumérateur. Chaque appel à GetEnumerator exécute le processus de copie complet détaillé ci-dessus. Le dictionnaire nouvellement créé contient des références aux objets clé et valeur internes d'origine, de sorte que vos valeurs de données mises en cache réelles ne sont pas dupliquées.

L'avertissement dans la documentation est correct. Évitez GetEnumerator () - y compris toutes les réponses ci-dessus qui utilisent des requêtes LINQ.

Une solution meilleure et plus flexible

Voici un moyen efficace de vider le cache qui s'appuie simplement sur l'infrastructure de surveillance des changements existante. Il offre également la flexibilité de vider le cache entier ou juste un sous-ensemble nommé et ne présente aucun des problèmes évoqués ci-dessus.

// 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
la source
8
Cela ressemble à une implémentation de la fonctionnalité Région manquante.
Jowen
Très agréable. J'ai essayé d'implémenter quelque chose en utilisant des moniteurs et des guides de mémoire enchaînés, mais cela commençait à devenir un peu moche alors que j'essayais de renforcer les fonctionnalités.
Chao
7
Je ne recommanderais pas ce modèle pour une utilisation générale. 1. Son lent, pas de faute de la mise en œuvre, mais la méthode de disposition est extrêmement lente. 2. Si votre expulsion des éléments du cache avec une expiration, Change monitor est toujours appelé. 3. Ma machine avalait tout le processeur et prenait beaucoup de temps pour effacer 30 000 éléments du cache lorsque j'exécutais des tests de performances. Quelques fois après avoir attendu plus de 5 minutes, je viens de tuer les tests.
Aaron M
1
@PascalMathys Malheureusement, il n'y a pas de meilleure solution que celle-ci. J'ai fini par l'utiliser, malgré les inconvénients, car c'est toujours une meilleure solution que d'utiliser l'énumération.
Aaron M
9
@AaronM Cette solution est-elle encore meilleure que de se débarrasser du cache et d'en instancier un nouveau?
RobSiklos
35

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

La solution de contournement est:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
magritte
la source
33
À partir de la documentation : La récupération d'un énumérateur pour une instance de MemoryCache est une opération de blocage et gourmande en ressources. Par conséquent, l'énumérateur ne doit pas être utilisé dans les applications de production.
TrueWill
3
@emberdude C'est exactement la même chose que récupérer un énumérateur - qu'est-ce que l'implémentation Select()fait pour vous?
RobSiklos
1
Personnellement, j'utilise ceci dans ma fonction de test unitaire [TestInitialize] pour vider le cache mémoire pour chaque test unitaire. Sinon, le cache persiste à travers les tests unitaires, donnant des résultats inattendus lorsque vous essayez de comparer les performances entre 2 fonctions.
Jacob Morrison
6
@JacobMorrison sans doute, les tests unitaires ne sont pas une "application de production" :)
Mels
1
@Mels sans doute, les tests unitaires devraient être écrits selon les mêmes standards que "application de production"! :)
Etherman
21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}
Roger Far
la source
3
Cela présente le même risque que la réponse de @ Tony; s'il vous plaît voir mon commentaire ci-dessous.
TrueWill
@TrueWill Qui est ou était @Tony?
Alex Angas
2
@AlexAngas - Il a peut-être changé son nom en magritte. Voir aussi stackoverflow.com/questions/4183270/…
TrueWill
10

Si les performances ne sont pas un problème, ce joli one-liner fera l'affaire:

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

Vous pouvez également faire quelque chose comme ceci:


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

J'ai couru à travers cela et, sur cette base, a écrit une méthode claire et parallèle légèrement plus efficace:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }
Pedro G. Dias
la source
1
L'avez-vous testé pour voir s'il est plus rapide (ou plus lent)?
Paul George
1

Je n'étais intéressé que par l'effacement du cache et j'ai trouvé cela comme une option, lors de l'utilisation du c # GlobalCachingProvider

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

une version un peu améliorée de la réponse 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
la source
0

Vous pouvez supprimer le cache MemoryCache.Default, puis redéfinir le singleton de champ privé sur null, pour qu'il recrée le MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
portes99
la source