суббота, 27 апреля 2013 г.

Кэширование часто запрашиваемых данных

Класс возник по результату трасировки приложения, оказалось что есть куча дублирующих запросов, трассировка выполнялась с помощью http://miniprofiler.com/, получил целую кучу дублированных запросов. Написал этот кэш менеджер, вместо 42 сек. получил время загрузки 3 секунды.

Примеры вызова:
// время кэширования 3 сек. количество элементов 100, фоновая очистка включена
OneTimeCash cash = new OneTimeCash(3,100,true); 
// время кэширования 400 сек. количество элементов 100, фоновая очистка выключена
var cashe1 = new OneTimeCash(400, 100, false);



using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace OneTimeCach
{
    ///

    ///  Кэширование предмета 1 раз. Используется для тяжелых операций типа обращения к БД
    ///
    /// тип кэшируемого значения
    public class OneTimeCash
    {
        ///

        ///Конструктор класса с инициализацией параметров по умолчанию.
        ///
        /// интервал очистки старых элементов в кэше по умолчанию 5 секунд
        /// Максимальное значение элементов в кэше
        /// Запускать очистку в фоновом потоке по умолчанию false, в этом случае очистка происходит при вставке элемента
        public OneTimeCash(int duration = 5, int maxcount = 100, bool backgroundCleanup = false)
        {
            this.duration = duration;
            this.isBackgroundCleanup = backgroundCleanup;
            this.maxCount = maxcount;
            this.items = new List(maxCount);
            if (isBackgroundCleanup)
            {
                isALive = true;
                Thread thread = new Thread(new ThreadStart(CleanapThreadWorker));
                thread.Start();
            }
        }

        ///

        /// Закрываем поток очистки кэша
        ///
        ~OneTimeCash()
        {
            this.isALive = false;
        }

        ///

        ///  Процедура фоновой очистки кэша
        ///
        void CleanapThreadWorker()
        {
            while (isALive)
            {
                Thread.Sleep(new TimeSpan(0, 0, duration));
                Cleanup();
            }
        }

        ///

        /// Контейнер для кэшируемых данных
        ///
        private class Container
        {
            ///

            /// Времф создания или обновления обьекта в кэше
            ///
            public DateTime Updated;

            ///

            /// Ключ для кэшированного обьекта
            ///
            public string Key;

            ///

            /// Кэшированный обьект
            ///
            public T Value;
        }

        ///

        /// Флаг фонового очищения данных кэша
        ///
        bool isBackgroundCleanup;

        ///

        /// Флаг указывающий на необходимость работы фоновой очистки данных
        ///
        bool isALive;


        public bool IsAlive { get { return isALive; } set { isALive = value; } }
        ///

        /// Промежуток времени жизни обьектов в кэше
        ///
        int duration;

        ///

        /// Максимальное количество обьектов в кэше
        ///
        int maxCount;

        ///

        /// Контейнер для хранения кэшированных обьектов
        ///
        List items;


        ///

        /// Обьект блокировки
        ///
        object locker = new object();

        ///

        /// Помещает элемент в кэш, и производит очистку устаревших элементов если не установлена автоочистка
        ///
        /// Ключ для сохранения кэшированного обьекта
        /// Кэшируемый обьект
        public void PutItem(string key, T cashedObject)
        {
            var item = items.FirstOrDefault(x => x.Key == key);
            if (item != null)
            {
                item.Updated = DateTime.Now;
                item.Value = cashedObject;
            }
            else
            {
                lock (locker)
                {
                    items.Add(new Container() { Key = key, Value = cashedObject, Updated = DateTime.Now });
                }
            }
            if (items.Count > maxCount )
            {
                Cleanup();
            }
        }

        ///

        /// Очистка от устаревших элементов кэша
        ///
        private void Cleanup()
        {
            var result = (from sel in items
                          where sel.Updated > DateTime.Now.AddSeconds(-duration)
                          orderby sel.Updated descending
                          select sel).Take(maxCount);
         
            List container = new List(maxCount);
            container.AddRange(result);
            lock (locker)
            {
                items = container;
            }

        }

        ///

        ///  Проверяет наличие обьекта в кэше
        ///
        /// Ключ для поиска
        ///
        public bool IsItemInCache(string key)
        {
            return items.Any(x => x.Key == key);
        }

        ///

        ///  Получаем закешированный обьект
        ///
        /// Ключ для получения кэшированного обьекта
        /// Возникает когда обьекта нет в кэше
        ///
        public T GetItem(string key)
        {
            var item = items.FirstOrDefault(x => x.Key == key);
            if (item != null)
            {
                return item.Value;
            }
            else
            {
                throw new ArgumentException(string.Format("No cashed object: {0}", key));
            }
        }
    }
}