Antecedentes:bibliotecas cache de procesos de seguridad para .NET
que mantienen varias aplicaciones Winforms y bibliotecas de clases que cualquiera podía hacer o que ya se benefician de almacenamiento en caché. También conozco el Caching Application Block y el espacio de nombres System.Web.Caching (que, por lo que he reunido, está perfectamente bien para usar fuera de ASP.NET).
He descubierto que, aunque las dos clases anteriores son técnicamente "seguras para subprocesos" en el sentido de que los métodos individuales están sincronizados, en realidad no parecen estar diseñados especialmente bien para escenarios de múltiples subprocesos. Específicamente, no implementan un GetOrAdd
method similar al de la nueva clase ConcurrentDictionary
en .NET 4.0.
Considero que este método es una primitiva para la funcionalidad de almacenamiento en caché/búsqueda, y obviamente los diseñadores de Framework también se dieron cuenta de esto - es por eso que los métodos existen en las colecciones concurrentes. Sin embargo, aparte del hecho de que todavía no estoy usando .NET 4.0 en aplicaciones de producción, un diccionario no es un caché completo: no tiene características como caducidad, almacenamiento persistente/distribuido, etc.
por qué esto es importante:
un diseño bastante típico en un "cliente enriquecido" aplicación (o incluso algunas aplicaciones web) es empezar a pre-carga de un caché tan pronto como se inicia la aplicación, el bloqueo si el el cliente solicita datos que aún no están cargados (posteriormente lo almacenan en caché para usarlo en el futuro). Si el usuario está trabajando en su flujo de trabajo rápidamente, o si la conexión de red es lenta, no es inusual que el cliente compita con el preloader, y realmente no tiene mucho sentido pedir los mismos datos dos veces. , especialmente si la solicitud es relativamente costosa.
Por lo tanto, parece ser dejado con algunas opciones igualmente pésimos:
No trate de hacer la operación atómica en absoluto, y correr el riesgo de que los datos sean cargados en dos ocasiones (y posiblemente tener dos hilos diferentes operando en diferentes copias);
acceso Serialize a la caché, lo que implica el bloqueo de la totalidad caché sólo para cargar un solo elemento ;
Comience a reinventar la rueda solo para obtener algunos métodos adicionales.
Aclaración: Ejemplo línea de tiempo
decir que cuando una aplicación se inicia, necesita cargar 3 conjuntos de datos que cada toma 10 segundos para cargar. Tenga en cuenta las siguientes dos líneas de tiempo:
00:00 - Start loading Dataset 1 00:10 - Start loading Dataset 2 00:19 - User asks for Dataset 2
En el caso anterior, si no utilizamos ningún tipo de sincronización, el usuario tiene que esperar un total de 10 segundos para los datos que estarán disponibles en 1 segundo, porque el El código verá que el elemento aún no se ha cargado en la caché e intentará volver a cargarlo.
00:00 - Start loading Dataset 1 00:10 - Start loading Dataset 2 00:11 - User asks for Dataset 1
En este caso, el usuario es buscar los datos que ya se encuentra en la caché. Pero si serializamos el acceso a la memoria caché, tendrá que esperar otros 9 segundos sin ningún motivo, porque el administrador de la memoria caché (sea lo que sea) no tenga conocimiento de la pregunta del elemento específico que se solicita, solo que "algo "se está solicitando y" algo "está en progreso.
La Pregunta:
¿Hay bibliotecas de caché para .NET (pre-4.0) que qué implementar dichas operaciones atómicas, como uno podría esperar de un cache de procesos de fallos?
O, alternativamente, ¿hay algún medio para extender una memoria caché existente "thread-safe" para apoyar este tipo de operaciones, sin serializar el acceso a la memoria caché (lo cual sería contrario al propósito de utilizar una aplicación segura para los subprocesos en el primer lugar)? Dudo que exista, pero tal vez estoy cansado e ignorando una solución obvia.
O ... ¿hay algo más que me falta? ¿Es una práctica estándar dejar que dos hilos de la competencia se peguen entre sí si, por casualidad, ambos solicitan el mismo artículo, al mismo tiempo, por primera vez o después de un vencimiento?
Tengo curiosidad por lo que quiere decir con no serializar el acceso al caché. De una manera u otra, el acceso al mismo recurso exacto (ya sea durante la creación inicial o después de la caducidad) tendría que ser serializado. Debería ser posible serializar el acceso a través de la clave en lugar de todo el caché, pero en algún punto, tendría que requerirse una serialización ... a menos que me faltara algo ... – jrista
@jrista: la serialización no es la única medios de seguridad de hilo; también hay bloqueos de lector-escritor, etc. Más al punto, sin embargo, una biblioteca segura para hilos debería manejar toda esta lógica por sí misma. Todas las implementaciones de caché que he visto son solo "thread safe" en el sentido de "múltiples hilos que invocan operaciones al mismo tiempo no pueden corromper el caché", pero lo que quiero es "soporte integrado para operaciones atómicas de múltiples pasos comunes, como carga lenta de elementos de caché ". – Aaronaught
Bueno, incluso en el caso de un bloqueo de lector-escritor ... si hay un único escritor, todo, todos los demás escritores y todos los lectores, están bloqueados hasta que se libere el único escritor. Fuera de las operaciones más básicas (como incrementos/decrementos, intercambios, etc. accesibles a través de la clase Interbloqueada), la serialización es generalmente una consecuencia inevitable de la sincronización de subprocesos. Puedes esconderlo y esconderlo todo lo que quieras, pero en algún momento sucede. Si quieres un caché seguro para subprocesos que hace lo que estás buscando, es posible, pero es probable que tengas que escribirlo tú mismo ... – jrista