Фон:
Я поддерживаю несколько приложений Winforms и библиотеки классов, которые могут или уже могут извлечь выгоду из кеширования. Я также знаю блок кэширования приложений и System.Web.Caching namespace (которое, из того, что я собрал, отлично подходит для использования вне 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), которые делают реализуют такие атомные операции, как можно было бы ожидать из поточно-безопасного кеша?
Или, в качестве альтернативы, есть ли какие-то средства для расширения существующего "поточно-безопасного" кеша для поддержки таких операций, без сериализации доступа к кешу (что приведет к поражению цели использования потока, безопасная реализация в первую очередь)? Я сомневаюсь, что есть, но, может быть, я просто устал и игнорирую очевидное обходное решение.
Или... есть что-то еще, что мне не хватает? Это просто стандартная практика, позволяющая двум конкурирующим нитям набирать друг друга друг на друга, если они оба будут запрашивать один и тот же элемент одновременно в первый раз или после истечения срока действия?