¿Cómo borrar MemoryCache?

100

He creado un caché usando la clase MemoryCache. Le agrego algunos elementos, pero cuando necesito recargar el caché, primero quiero borrarlo. ¿Cuál es la forma más rápida de hacer esto? ¿Debo recorrer todos los elementos y eliminarlos uno a la vez o hay una manera mejor?

Retrocodificador
fuente
1
Para .NET core, consulte esta respuesta.
Makla

Respuestas:

61

Dispose MemoryCache existente y cree un nuevo objeto MemoryCache.

GvS
fuente
3
Inicialmente usé MemoryCache.Default, lo que hizo que Dispose me diera algo de dolor. Aún así, Dispose terminó siendo la mejor solución que pude encontrar. Gracias.
LaustN
11
@LaustN, ¿puedes dar más detalles sobre el "dolor" causado por MemoryCache.Default? Actualmente estoy usando MemoryCache.Default ... La documentación de MemoryCache de MSDN me hace preguntarme si se recomienda desechar y recrear: "No cree instancias de MemoryCache a menos que sea necesario. Si crea instancias de caché en aplicaciones cliente y web, las instancias de MemoryCache deberían crearse al principio del ciclo de vida de la aplicación ". ¿Esto se aplica a .Default? No estoy diciendo que usar Dispose esté mal, honestamente, solo busco una aclaración sobre todo esto.
ElonU Webdev
8
Pensó que valía la pena mencionar que Dispose hace invocar ninguna CacheEntryRemovedCallbackunido a los elementos almacenados en caché actuales.
Mike Guthrie
8
@ElonU: la siguiente respuesta de Stack Overflow explica algunos de los problemas que puede encontrar al deshacerse de la instancia predeterminada: stackoverflow.com/a/8043556/216440 . Para citar: "El estado de la caché está configurado para indicar que la caché está eliminada. Cualquier intento de llamar a métodos públicos de almacenamiento en caché que cambian el estado de la caché, como los métodos que agregan, eliminan o recuperan entradas de caché, pueden causar comportamiento. Por ejemplo, si llama al método Set después de eliminar el caché, se produce un error de no operación ".
Simon Tewsi
56

El problema de la enumeración

La sección Comentarios de MemoryCache.GetEnumerator () advierte: "Recuperar un enumerador para una instancia de MemoryCache es una operación de bloqueo y que consume muchos recursos. Por lo tanto, el enumerador no debe usarse en aplicaciones de producción".

Este es el motivo , explicado en pseudocódigo de la implementación 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

Dado que la implementación divide la caché en varios objetos Dictionary, debe reunir todo en una sola colección para poder devolver un enumerador. Cada llamada a GetEnumerator ejecuta el proceso de copia completo detallado arriba. El Diccionario recién creado contiene referencias a la clave interna original y a los objetos de valor, por lo que sus valores de datos almacenados en caché reales no se duplican.

La advertencia en la documentación es correcta. Evite GetEnumerator (), incluidas todas las respuestas anteriores que usan consultas LINQ.

Una solución mejor y más flexible

Esta es una forma eficiente de borrar la caché que simplemente se basa en la infraestructura de monitoreo de cambios existente. También proporciona la flexibilidad de borrar todo el caché o solo un subconjunto con nombre y no tiene ninguno de los problemas discutidos anteriormente.

// 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
fuente
8
Parece una implementación para la funcionalidad de Región que falta.
Jowen
Muy agradable. He estado tratando de implementar algo usando guids y monitores de memoria caché encadenados, pero estaba empezando a ponerse un poco feo cuando intenté ajustar la funcionalidad.
Chao
7
No recomendaría este patrón para uso general. 1. Es lento, no es culpa de la implementación, pero el método de eliminación es extremadamente lento. 2. Si sus elementos de desalojo de la caché con un vencimiento, aún se llama a Cambiar monitor. 3. Mi máquina se tragaba toda la CPU y tardaba mucho en borrar 30k elementos de la caché cuando estaba ejecutando pruebas de rendimiento. Unas cuantas veces, después de esperar más de 5 minutos, acabo de terminar las pruebas.
Aaron M
1
@PascalMathys Desafortunadamente, no hay mejor solución que esta. Terminé usándolo, a pesar de las desventajas, ya que sigue siendo una mejor solución que usar la enumeración.
Aaron M
9
@AaronM ¿Esta solución es aún mejor que simplemente deshacerse del caché y crear una nueva instancia?
RobSiklos
35

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

La solución alternativa es:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
magritte
fuente
33
De la documentación : Recuperar un enumerador para una instancia de MemoryCache es una operación de bloqueo y que consume muchos recursos. Por lo tanto, el enumerador no debe usarse en aplicaciones de producción.
TrueWill
3
@emberdude Es exactamente lo mismo que recuperar un enumerador: ¿qué crees que hace la implementación Select()?
RobSiklos
1
Personalmente, estoy usando esto en mi función de prueba de unidad [TestInitialize] para borrar el caché de memoria para cada prueba de unidad. De lo contrario, la caché persiste en las pruebas unitarias y da resultados no deseados al intentar comparar el rendimiento entre 2 funciones.
Jacob Morrison
6
@JacobMorrison podría decirse que las pruebas unitarias no son una "aplicación de producción" :)
Mels
1
@Mels podría decirse que las pruebas unitarias deben escribirse con los mismos estándares que la "aplicación de producción". :)
Etherman
21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}
Roger Far
fuente
3
Esto tiene el mismo riesgo que la respuesta de @ Tony; por favor vea mi comentario debajo de eso.
TrueWill
@TrueWill ¿Quién es o fue @Tony?
Alex Angas
2
@AlexAngas - Puede que haya cambiado su nombre a magritte. Consulte también stackoverflow.com/questions/4183270/…
TrueWill
10

Si el rendimiento no es un problema, este buen resumen hará el truco:

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

También puedes hacer algo como esto:


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

Me encontré con esto y, basándose en él, escribí un método claro paralelo ligeramente más efectivo:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }
Pedro G. Dias
fuente
1
¿Lo probó para ver si es más rápido (o más lento)?
Paul George
1

Solo estaba interesado en borrar el caché y encontré esto como una opción, al usar c # GlobalCachingProvider

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

una versión un poco mejorada de magritte answer.

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

Puede deshacerse del caché MemoryCache.Default y luego restablecer el campo privado singleton en nulo, para que vuelva a crear el MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
puertas99
fuente