Como limpar o MemoryCache?

100

Eu criei um cache usando a classe MemoryCache. Adiciono alguns itens, mas quando preciso recarregar o cache, quero limpá-lo primeiro. Qual é a maneira mais rápida de fazer isso? Devo percorrer todos os itens e removê-los um de cada vez ou existe uma maneira melhor?

Retrocoder
fonte
1
Para o .NET core, verifique esta resposta.
Makla

Respostas:

61

Dispose o MemoryCache existente e crie um novo objeto MemoryCache.

GvS
fonte
3
Eu usei inicialmente MemoryCache.Default, fazendo com que Dispose me desse algum sofrimento. Mesmo assim, Dispose acabou sendo a melhor solução que consegui encontrar. Obrigado.
LaustN
11
@LaustN você pode entrar em detalhes sobre o "pesar" causado por MemoryCache.Default? No momento, estou usando MemoryCache.Default ... A documentação do MemoryCache do MSDN me faz pensar se é recomendável descartar e recriar: "Não crie instâncias de MemoryCache a menos que seja necessário. Se você criar instâncias de cache em aplicativos cliente e Web, as instâncias de MemoryCache devem ser criado no início do ciclo de vida do aplicativo. " Isso se aplica a .Default? Não estou dizendo que o uso de Dispose é errado, honestamente, estou apenas procurando esclarecimentos sobre tudo isso.
ElonU Webdev
8
Pensei que era pena mencionar que Dispose faz invocar qualquer CacheEntryRemovedCallbackanexado aos itens em cache atuais.
Mike Guthrie
8
@ElonU: A seguinte resposta do Stack Overflow explica algumas das dores que você pode encontrar ao descartar a instância padrão: stackoverflow.com/a/8043556/216440 . Para citar: "O estado do cache é definido para indicar que o cache foi descartado. Qualquer tentativa de chamar métodos de armazenamento público que alteram o estado do cache, como métodos que adicionam, removem ou recuperam entradas de cache, pode causar resultados inesperados comportamento. Por exemplo, se você chamar o método Set depois que o cache for descartado, ocorrerá um erro no-op. "
Simon Tewsi
56

O problema com enumeração

A seção MemoryCache.GetEnumerator () Comentários avisa: "Recuperar um enumerador para uma instância de MemoryCache é uma operação que consome muitos recursos e bloqueia. Portanto, o enumerador não deve ser usado em aplicativos de produção."

Aqui está o motivo , explicado no pseudocódigo da implementação 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

Como a implementação divide o cache em vários objetos Dictionary, ela deve reunir tudo em uma única coleção para devolver um enumerador. Cada chamada para GetEnumerator executa o processo de cópia completo detalhado acima. O Dicionário recém-criado contém referências à chave interna original e aos objetos de valor, para que os valores reais dos dados em cache não sejam duplicados.

O aviso na documentação está correto. Evite GetEnumerator () - incluindo todas as respostas acima que usam consultas LINQ.

Uma solução melhor e mais flexível

Esta é uma maneira eficiente de limpar o cache que simplesmente se baseia na infraestrutura de monitoramento de alterações existente. Ele também fornece a flexibilidade de limpar todo o cache ou apenas um subconjunto nomeado e não tem nenhum dos problemas discutidos acima.

// 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
fonte
8
Parece uma implementação para a funcionalidade de região ausente.
Jowen,
Muito agradável. Tenho tentado implementar algo usando monitores e guids de cache de memória encadeados, mas estava começando a ficar um pouco feio enquanto tentava aumentar a funcionalidade.
Chao
7
Eu não recomendaria esse padrão para uso geral. 1. É lento, não é culpa da implementação, mas o método de descarte é extremamente lento. 2. Se você estiver removendo itens do cache com uma expiração, o monitor de mudança ainda será chamado. 3. Minha máquina estava engolindo toda a CPU e demorando muito para limpar 30k itens do cache quando eu estava executando testes de desempenho. Algumas vezes, depois de esperar 5+ minutos, acabei de matar os testes.
Aaron M
1
@PascalMathys Infelizmente, não há solução melhor do que essa. Acabei usando, apesar das desvantagens, pois ainda é uma solução melhor do que usar a enumeração.
Aaron M
9
@AaronM Esta solução ainda é melhor do que apenas descartar o cache e instanciar um novo?
RobSiklos
35

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

A solução alternativa é:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}
magritte
fonte
33
Da documentação : A recuperação de um enumerador para uma instância do MemoryCache é uma operação que consome muitos recursos e bloqueia. Portanto, o enumerador não deve ser usado em aplicativos de produção.
TrueWill
3
@emberdude É exatamente o mesmo que recuperar um enumerador - o que você acha que a implementação Select()faz?
RobSiklos
1
Pessoalmente, estou usando isso em minha função de teste de unidade [TestInitialize] para limpar o cache de memória para cada teste de unidade. Caso contrário, o cache persiste nos testes de unidade, fornecendo resultados indesejados ao tentar comparar o desempenho entre 2 funções.
Jacob Morrison
6
@JacobMorrison sem dúvida, os testes de unidade não são um "aplicativo de produção" :)
Mels
1
@Mels, sem dúvida, os testes de unidade devem ser escritos com os mesmos padrões de "aplicativo de produção"! :)
Etherman
21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}
Roger Far
fonte
3
Isso apresenta o mesmo risco da resposta de @Tony; por favor, veja meu comentário abaixo disso.
TrueWill
@TrueWill Quem é ou foi @Tony?
Alex Angas
2
@AlexAngas - Ele pode ter mudado seu nome para magritte. Consulte também stackoverflow.com/questions/4183270/…
TrueWill
10

Se o desempenho não for um problema, este belo one-liner fará o truque:

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

Você também pode fazer algo assim:


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

Encontrei isso e, com base nisso, escrevi um método claro paralelo um pouco mais eficaz:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }
Pedro G. Dias
fonte
1
Você testou para ver se é mais rápido (ou mais lento)?
Paul George
1

Eu estava interessado apenas em limpar o cache e achei isso como uma opção, ao usar o c # GlobalCachingProvider

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

uma versão um pouco melhorada da resposta 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
fonte
0

Você pode descartar o cache MemoryCache.Default e, em seguida, reconfigurar o singleton do campo privado como nulo, para fazê-lo recriar o MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
portas 99
fonte