Как очистить MemoryCache?


100

Я создал кеш с помощью класса MemoryCache. Я добавляю в него несколько элементов, но когда мне нужно перезагрузить кеш, я хочу сначала его очистить. Как это сделать быстрее всего? Должен ли я перебирать все элементы и удалять их по одному или есть лучший способ?


1
Для ядра .NET проверьте этот ответ.
Makla

Ответы:


61

Dispose существующий MemoryCache и создайте новый объект MemoryCache.


3
Сначала я использовал MemoryCache.Default, из-за чего Dispose меня огорчил. Тем не менее, Dispose оказался лучшим решением, которое я смог найти. Спасибо.
LaustN 06

11
@LaustN, можете ли вы подробнее рассказать о "горе", вызванном MemoryCache.Default? В настоящее время я использую MemoryCache.Default ... Документация MSDN MemoryCache заставляет меня задуматься, рекомендуется ли удаление и воссоздание: «Не создавайте экземпляры MemoryCache, если это не требуется. Если вы создаете экземпляры кеша в клиентских и веб-приложениях, экземпляры MemoryCache должны должны быть созданы на ранних этапах жизненного цикла приложения ". Применимо ли это к .Default? Я не говорю, что использование Dispose неправильно, я просто ищу разъяснений по всему этому.
ElonU Webdev

8
Думал , что это стоит упомянуть , что Dispose делает вызвать любой CacheEntryRemovedCallbackприложенные к текущим пунктам кэшированных.
Майк Гатри

8
@ElonU: Следующий ответ о переполнении стека объясняет некоторые проблемы, с которыми вы можете столкнуться при удалении экземпляра по умолчанию: stackoverflow.com/a/8043556/216440 . Цитата: «Состояние кеша установлено так, чтобы указывать на то, что кеш удален. Любая попытка вызвать общедоступные методы кеширования, которые изменяют состояние кеша, например методы, которые добавляют, удаляют или извлекают записи кеша, может вызвать неожиданные Например, если вы вызываете метод Set после удаления кэша, возникает ошибка бездействия ".
Саймон Тевси

56

Проблема с перечислением

В разделе «Замечания» MemoryCache.GetEnumerator () предупреждается: «Получение перечислителя для экземпляра MemoryCache является ресурсоемкой и блокирующей операцией. Следовательно, перечислитель не следует использовать в производственных приложениях».

Вот почему , объясненное в псевдокоде реализации 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

Поскольку реализация разделяет кеш на несколько объектов Dictionary, она должна собрать все вместе в единую коллекцию, чтобы вернуть перечислитель. Каждый вызов GetEnumerator выполняет процесс полного копирования, описанный выше. Вновь созданный Словарь содержит ссылки на исходные объекты внутреннего ключа и значения, поэтому ваши фактические значения кэшированных данных не дублируются.

Предупреждение в документации верное. Избегайте GetEnumerator () - включая все приведенные выше ответы, в которых используются запросы LINQ.

Лучшее и более гибкое решение

Вот эффективный способ очистки кеша, который просто основан на существующей инфраструктуре мониторинга изменений. Он также обеспечивает гибкость для очистки либо всего кеша, либо только указанного подмножества и не имеет никаких проблем, описанных выше.

// 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;
        }
    }
}

8
Похоже на реализацию отсутствующей функциональности региона.
Jowen

Очень хорошо. Я пытался реализовать что-то, используя связанные мониторы и руководства кэша памяти, но это начинало становиться немного уродливым, когда я пытался усилить функциональность.
Чао

7
Я бы не рекомендовал этот шаблон для общего использования. 1. Это медленно, не виной реализации, но метод удаления очень медленный. 2. Если вы удаляете элементы из кеша с истечением срока действия, монитор изменений все равно вызывается. 3. Моя машина проглатывала весь процессор, и мне требовалось очень много времени, чтобы очистить 30 тыс. Элементов из кеша, когда я проводил тесты производительности. Пару раз после ожидания 5+ минут просто убивал тесты.
Aaron M

1
@PascalMathys К сожалению, лучшего решения, чем это, нет. В итоге я использовал его, несмотря на недостатки, поскольку это все же лучшее решение, чем использование перечисления.
Aaron M

9
@AaronM Это решение лучше, чем просто удаление кеша и создание нового?
RobSiklos

35

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

Обходной путь:

List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}

34
Из документации : Получение перечислителя для экземпляра MemoryCache - ресурсоемкая и блокирующая операция. Следовательно, перечислитель не следует использовать в производственных приложениях.
TrueWill

3
@emberdude Это точно так же, как получение перечислителя - что, по вашему мнению, Select()делает реализация ?
RobSiklos

1
Лично я использую это в своем модульном тесте [TestInitialize], чтобы очистить кеш памяти для каждого модульного теста. В противном случае кеш сохраняется во время модульных тестов, давая непредвиденные результаты при попытке сравнить производительность двух функций.
Джейкоб Моррисон

6
@JacobMorrison, возможно, модульные тесты не являются «производственным приложением» :)
Mels

1
@Mels, возможно, юнит-тесты должны быть написаны по тем же стандартам, что и "производственное приложение"! :)
Etherman

21
var cacheItems = cache.ToList();

foreach (KeyValuePair<String, Object> a in cacheItems)
{
    cache.Remove(a.Key);
}

3
Это тот же риск, что и ответ @Tony; пожалуйста, посмотрите мой комментарий под этим.
TrueWill

@TrueWill Кто такой @Tony?
Alex Angas

2
@AlexAngas - Возможно, он изменил свое имя на Магритт. См. Также stackoverflow.com/questions/4183270/…
TrueWill

10

Если производительность не является проблемой, то этот хороший однострочный текст поможет:

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

7

Вроде есть метод Trim .

Итак, чтобы очистить все содержимое, вам просто нужно сделать

cache.Trim(100)

РЕДАКТИРОВАТЬ: покопавшись еще немного, кажется, что изучение Trim не стоит вашего времени

https://connect.microsoft.com/VisualStudio/feedback/details/831755/memorycache-trim-method-doesnt-evict-100-of-the-items

Как очистить System.Runtime.Caching.MemoryCache


3

Вы также можете сделать что-то вроде этого:


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

3

Пробежавшись по этому поводу, на его основе написал чуть более эффективный метод параллельной очистки:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }

1
Вы проверяли, быстрее (или медленнее)?
Пол Джордж

1

Меня интересовала только очистка кеша, и я нашел это как вариант при использовании c # GlobalCachingProvider

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

0

немного улучшенная версия ответа Магритта.

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

0

Вы можете избавиться от кеша MemoryCache.Default, а затем переустановить синглтон частного поля на null, чтобы он воссоздал MemoryCache.Default.

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.