Справочная информация:
Я поддерживаю несколько приложений Winforms и библиотек классов, которые могут или уже могут извлечь выгоду из кэширования. Мне также известно о блоке приложения для кэширования и пространстве имен System.Web.Caching (которое, насколько я знаю, вполне подходит для использования вне ASP.NET). .
Я обнаружил, что, хотя оба вышеперечисленных класса являются технически «поточно-ориентированными» в том смысле, что отдельные методы синхронизированы, на самом деле они не очень хорошо разработаны для многопоточных сценариев. В частности, они не реализуют GetOrAdd
метод , подобный методу в новом классе ConcurrentDictionary
в .NET 4.0.
Я считаю такой метод примитивом для функций кэширования / поиска, и, очевидно, разработчики Framework это тоже поняли - поэтому методы существуют в параллельных коллекциях. Однако, несмотря на то, что я пока не использую .NET 4.0 в производственных приложениях, словарь не является полноценным кешем - он не имеет таких функций, как истечение срока хранения, постоянное / распределенное хранилище и т. Д.
Почему это важно:
Довольно типичный дизайн в приложении «богатого клиента» (или даже в некоторых веб-приложениях) состоит в том, чтобы начать предварительную загрузку кэша сразу после запуска приложения, блокируя, если клиент запрашивает данные, которые еще не загружены (впоследствии кэшируется это для будущего использования). Если пользователь быстро просматривает свой рабочий процесс или медленное сетевое соединение, для клиента нет ничего необычного в том, чтобы конкурировать с предзагрузчиком, и на самом деле не имеет большого смысла запрашивать одни и те же данные дважды особенно если запрос относительно дорогой.
Так что мне кажется, что у меня осталось несколько одинаково паршивых вариантов:
Ни в коем случае не пытайтесь сделать операцию атомарной, и рискуйте загрузкой данных дважды (и, возможно, двумя разными потоками, работающими с разными копиями);
Сериализация доступа к кешу, что означает блокировку всего кеша только для загрузки отдельного элемента ;
Начните изобретать велосипед, чтобы получить несколько дополнительных методов.
Уточнение: Пример временной шкалы
Скажите, что при запуске приложения ему нужно загрузить 3 набора данных, каждый из которых загружается за 10 секунд. Рассмотрим следующие два графика времени:
00:00 - Start loading Dataset 1
00:10 - Start loading Dataset 2
00:19 - User asks for Dataset 2
В приведенном выше случае, если мы не используем какую-либо синхронизацию, пользователь должен ждать целых 10 секунд для данных, которые будут доступны через 1 секунду, потому что код увидит, что элемент еще не загружен в кеш и попробуйте перезагрузить его.
00:00 - Start loading Dataset 1
00:10 - Start loading Dataset 2
00:11 - User asks for Dataset 1
В этом случае пользователь запрашивает данные, которые уже в кэше. Но если мы сериализуем доступ к кешу, ему придется ждать еще 9 секунд без всякой причины, потому что менеджер кеша (что бы это ни было) не знает о том, что специфический элемент запрашивается, только то «что-то» запрашивается, а «что-то» выполняется.
Вопрос:
Существуют ли какие-либо библиотеки кэширования для .NET (до версии 4.0), в которых do реализует такие атомарные операции, как можно ожидать от поточно-безопасного кеша?
Или, в качестве альтернативы, существуют ли какие-либо средства для расширения существующего "поточно-ориентированного" кэша для поддержки таких операций, без сериализации доступа к кешу (что противоречило бы цели использования поточно-безопасного реализация в первую очередь)? Я сомневаюсь, что есть, но, возможно, я просто устал и игнорирую очевидный обходной путь.
Или ... что-то еще мне не хватает? Является ли обычной практикой позволять двум конкурирующим потокам обкатывать друг друга, если они оба запрашивают один и тот же элемент в одно и то же время в первый раз или после истечения срока действия?