Как стать автором
Обновить

Простой кэш в памяти

Время на прочтение2 мин
Количество просмотров10K

Добрый день!



Понадобилось тут сделать совсем примитивный кэш, чтобы лишний раз в базу не лазить. При этом данные в базе статичные, и вопрос не столько в обновлении данных, сколько в том, когда их выбросить, чтобы просто не занимать память, ну и простота использования конечно важна. Сначала хотел использовать MemoryCache, но мне показалось он слишком замороченный, да ещё и без строгой типизации.

За примером реализации прошу под кат.


Реализация

Общая идея такая: кэш должен быть удобным и прозрачным в использовании. Я вдохновился интерфейсом класса ThreadLocal. Поэтому в конструкторе требуется фабричный метод для получения исходных значений, а потом они уже кэшируются. Опять же, в моём случае, я посчитал вполне логичным держать данные на слабых ссылках (WeakReference), таким образом, пока данные нужны, кто-то их использует, они будут доступны. Дальше, по мере выделения новой памяти, данные из кэша будут вытесняться.
Есть всего два метода: индексатор, который вернёт данные либо из источника, либо из кэша.
А также метод CleanCache, чтобы очищать словарь от мёртвых ссылок и таким образом устранить утечку памяти. В реальных условиях далеко не всегда это необходимо. Например, в моих условиях новые записи в кэше будут появляться всего несколько раз в день, так что даже за годы работы ссылки не сожрут сколько-то значительный объём памяти.

Цель публикации

В основном, мне интересно услышать мнение о моей реализации, как бы вы сделали, есть ли ошибки. Ну и если кому-то это поможет — берите, используйте :)

Исходник


public class WeakCache<TKey,TValue> where TValue : class
    {
        const int cacheCleanInterval = 60;
        private readonly Func<TKey, TValue> getter;
        private readonly Dictionary<TKey, WeakReference> data = new Dictionary<TKey, WeakReference>();
        private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
        private DateTime lastCacheClean = DateTime.MinValue;
        
        public WeakCache(Func<TKey,TValue> getter)
        {
            this.getter = getter;
        }

        public TValue this[TKey key]
        {
            get
            {
                CleanCache();
                try
                {
                    rwLock.EnterUpgradeableReadLock();
                    WeakReference wr;
                    TValue val;
                    if (data.TryGetValue(key, out wr))
                    {
                        val = (TValue)wr.Target;
                        if (val != null)
                            return val;
                    }
                    try
                    {
                        rwLock.EnterWriteLock();
                        if (data.TryGetValue(key, out wr))
                        {
                            val = (TValue)wr.Target;
                            if (val != null)
                                return val;
                        }
                        data[key] = new WeakReference(val = getter(key));
                        return val;
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                    }
                }
                finally
                {
                    rwLock.ExitUpgradeableReadLock();
                }
            }
        }

        void CleanCache()
        {
            if ((DateTime.Now - lastCacheClean).TotalSeconds > cacheCleanInterval)
            {
                try
                {
                    rwLock.EnterWriteLock();
                    if ((DateTime.Now - lastCacheClean).TotalSeconds > cacheCleanInterval)
                    {
                        lastCacheClean = DateTime.Now;
                        var refs = data.ToArray();
                        foreach (var weakReference in refs)
                        {
                            if (!weakReference.Value.IsAlive)
                                data.Remove(weakReference.Key);
                        }
                    }
                }
                finally 
                {
                    rwLock.ExitWriteLock();
                }
            }
        }
    }
Теги:
Хабы:
Всего голосов 16: ↑5 и ↓11-6
Комментарии12

Публикации

Истории

Работа

Ближайшие события