Добрый день!
Понадобилось тут сделать совсем примитивный кэш, чтобы лишний раз в базу не лазить. При этом данные в базе статичные, и вопрос не столько в обновлении данных, сколько в том, когда их выбросить, чтобы просто не занимать память, ну и простота использования конечно важна. Сначала хотел использовать 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(); } } } }
ссылка на оригинал статьи http://habrahabr.ru/post/185616/
Добавить комментарий