+7 (495) 229-0436   shopadmin@itshop.ru 119334, г. Москва, ул. Бардина, д. 4, корп. 3
 
 
Вход
 
 
Каталог
 
 
Подписка на новости
Новости ITShop
Windows 7 и Office: Новости и советы
Обучение и сертификация Microsoft
Вопросы и ответы по MSSQLServer
Delphi - проблемы и решения
Adobe Photoshop: алхимия дизайна
 
Ваш отзыв
Оцените качество магазина ITShop.ru на Яндекс.Маркете. Если вам нравится наш магазин - скажите об этом Google!
 
 
Способы оплаты
 
Курс расчета
 
 1 у.е. = 93.25 руб.
 
 Цены показывать:
 
 
 
 
  
Новости, статьи, акции
 

Расширяем и улучшаем Cache в ASP.NET

15.12.2009 15:02

Про ASP.NET-объект Cache наверняка знает каждый web-разработчик на платформе .NET. Совсем не странно, ведь это единственное решение для кэширования данных web-приложения в ASP.NET, доступное прямо из коробки.
Достаточно функциональный и легкий, снабженный механизмами приоритета, вытеснения, зависимостей и обратных вызовов, Cache хорошо подходит для небольших приложений, работая внутри AppDomain. Кажется, Microsoft предусмотрела все, что необходимо… Но я, тем не менее, хочу сделать его еще немного лучше. Чем же именно?

Синхронизация обновлений

Закэшированным данным, как и многому в нашем мире, со временем свойственно терять актуальность. Поэтому, по истечении отведенного интервала времени или при изменении одной из зависимостей, сохраненный элемент исчезнет из кэша, и нам придется положить его туда заново. MSDN

расскажет

нам, как это сделать, и мы напишем так:

List<Product> products;
products = (List<Product>)Cache["Products"];
if (products == null)
{
  products = db.Products.ToList();
  Cache.Insert("Products", products);
}

* This source code was highlighted with Source Code Highlighter.

Все выглядит правильно, но ровно до тех пор, пока мы не осознаем, что код может выполняться одновременно в нескольких потоках. И в этих нескольких строчках мы только что организовали классическое состояние гонки (race condition). Ничего страшного, конечно, не произойдет, просто элемент кэша будет обновлен несколько раз, и каждый раз для этого мы обратимся к базе данных. Но это лишняя работа, и ее можно избежать, применив обычную double-check блокировку. Вот так:

private static object _lock = new object();

...

object value;
if ((value = Cache["Products"]) == null)
{
  lock (_lock)
  {
   if ((value = Cache["Products"]) == null)
   {
      value = db.Products.ToList();
      Cache.Insert("Products", value);
   }
  }
}
var products = (List<Product>)value;

* This source code was highlighted with Source Code Highlighter.

Таким образом, мы гарантируем, что только один поток отправится в базу данных за списком товаров, а остальные подождут его возвращения. Можно писать такой код всякий раз, когда мы работаем с кэшем, но лучше реализовать расширение объекта Cache при помощи extension-метода.

Итак,

public static T Get<T>(this Cache cache, string key, object @lock, Func<T> selector,
  DateTime absoluteExpiration)
{
   object value;
   if ((value = cache.Get(key)) == null)
   {
     lock (@lock)
     {
      if ((value = cache.Get(key)) == null)
      {
        value = selector();
        cache.Insert(key, value, null,
         absoluteExpiration, Cache.NoSlidingExpiration,
         CacheItemPriority.Normal, null);
      }
   }
  }
  return (T)value;
}
* This source code was highlighted with Source Code Highlighter.

Если в кэше нашелся элемент с заданным ключом, метод просто возвратит его, а в противном случае установит блокировку и выполнит загрузку. Конструкции

value = Cache.Get(key)

нужны для того, чтобы не получить такую же гонку при удалении элемента кэша в другом потоке. Теперь для получения нашего списка товаров мы можем написать только одну строку, а все остальное наше расширение возьмет на себя. Перегрузки можно добавить по вкусу :)

private static object myLock = new object();
...
var products = Cache.Get<List<Product>>("Products", myLock,
  () => db.Products.ToList(), DateTime.Now.AddMinutes(10));

* This source code was highlighted with Source Code Highlighter.

Итак, с одной задачей мы расправились, но есть еще кое-что интересное. К примеру, ситуация, когда необходимо объявить невалидными сразу несколько связанных элементов кэша. ASP.NET Cache предоставляет нам возможность создания зависимости от одного или нескольких элементов. Примерно так:

string[] dependencies = { "parent" };
Cache.Insert("child", someData,
  new CacheDependency(null, dependencies));

* This source code was highlighted with Source Code Highlighter.

И при обновлении элемента

parent

элемент

child

будет удален. Пока ничего не напоминает? Что ж, еще немного кода, и у нас появится полноценная…

Поддержка тегов и групповой инвалидации

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

public static CacheDependency CreateTagDependency(
  this Cache cache, params string[] tags)
{
  if (tags == null // tags.Length < 1)
   return null;

  long version = DateTime.UtcNow.Ticks;
  for (int i = 0; i < tags.Length; ++i)
  {
   cache.Add("_tag:" + tags[i], version, null,
     DateTime.MaxValue, Cache.NoSlidingExpiration,
     CacheItemPriority.NotRemovable, null);
  }
  return new CacheDependency(null, tags.Select(s =>
   "_tag:" + s).ToArray());
}

* This source code was highlighted with Source Code Highlighter.

Здесь в качестве значения версии тега я использую текущее время, как рекомендовалось в

статье о Memcached

, но в нашем случае сравнивать ничего не придется, этим займется ASP.NET. Теперь при добавлении элемента в кэш мы можем легко и просто создать зависимость от указанных нами тегов.

Сache.Insert("key", value, Сache.CreateTagDependency("tag1", "tag2"));

* This source code was highlighted with Source Code Highlighter.

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

public static void Invalidate(this Cache cache, params string[] tags)
{
  long version = DateTime.UtcNow.Ticks;
  for (int i = 0; i < tags.Length; ++i)
  {
   cache.Insert("_tag:" + tags[i], version, null,
     DateTime.MaxValue, Cache.NoSlidingExpiration,
     CacheItemPriority.NotRemovable, null);
  }
}

* This source code was highlighted with Source Code Highlighter.

Обратите внимание на то, что в методе, создающем зависимость от тегов, использовался метод

cache.Add

, а здесь -

cache.Insert

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

На этом, кажется, и все…

Я требую продолжения банкета!

А продолжать тут еще есть куда. Например, можно доработать приведенный здесь метод Get так, чтобы вместо немедленного удаления данные кэша временно "переезжали" в другую ячейку, и вместо блокировки возвращать запрошенную информацию из нее, пока новые данные загружаются в кэш.

Можно вместо extension-методов сделать некую абстракцию провайдера кэширования и работать с любым хранилищем без изменения кода приложения или полностью отключать кэш при отладке, использовать IoC… да мало ли что!

И я надеюсь, что подходы, описанные в моей статье, окажутся полезными для вас ;)

UPDATE

: Посмотрев незамыленным глазом на собственный код, я узрел в нем одну нехорошую вещь - блокировка при синхронизации выставлялась на весь кэш целиком. Поэтому я изменил extension-метод

Get

с тем, чтобы он принимал пользовательский объект для блокировки.

Ссылки по теме

  
Помощь
Задать вопрос
 программы
 обучение
 экзамены
 компьютеры
Бесплатный звонок
ICQ-консультанты
Skype-консультанты

Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 
Бестселлеры
Курсы обучения "Atlassian JIRA - система управления проектами и задачами на предприятии"
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2019. Все языки. Электронный ключ
Курс "Oracle. Программирование на SQL и PL/SQL"
Курс "Основы TOGAF® 9"
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Курс "Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации"
 

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

В нашем магазине вы можете приобрести лицензионное ПО выбрав необходимое из широкого спектра и ассортимента по самым доступным ценам. Наши менеджеры любезно помогут определиться с выбором ПО, которое необходимо именно вам. Также мы проводим учебные курсы. Мы приглашаем к сотрудничеству учебные центры, организаторов семинаров и бизнес-тренингов, преподавателей. Сфера сотрудничества - продвижение бизнес-тренингов и курсов обучения по информационным технологиям.



 

О нас

 
Главная
Каталог
Новинки
Акции
Вакансии
 

Помощь

 
Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 

Способы оплаты

 

Проекты Interface Ltd.

 
Interface.ru   ITShop.ru   Interface.ru/training   Olap.ru   ITnews.ru  
 

119334, г. Москва, ул. Бардина, д. 4, корп. 3
+7 (495) 229-0436   shopadmin@itshop.ru
Проверить аттестат
© ООО "Interface Ltd."
Продаем программное обеспечение с 1990 года