Problema: necesito implementar caché de objetos. El caché debe ser seguro para subprocesos y debe completar los valores a pedido (carga diferida). Los valores se recuperan a través del servicio web mediante Key (operación lenta). Así que he decidido usar ConcurrentDictionary y su método GetOrAdd() que tiene un método de fábrica de valores suponiendo que la operación es atómica y sincronizada. Desafortunadamente, encontré la siguiente declaración en el artículo de MSDN: Cómo: Agregar y quitar elementos de un diccionario concurrente :
Además, aunque todos los métodos de ConcurrentDictionary son seguros para subprocesos, no todos los métodos son atómicos, específicamente GetOrAdd y AddOrUpdate. El delegado de usuario que se pasa a estos métodos se invoca fuera del bloqueo interno del diccionario.
Bueno, eso es desafortunado, pero aún no responde mi respuesta por completo.
Pregunta: ¿Se invoca value factory solo una vez por tecla? En mi caso específico: ¿es posible que múltiples subprocesos que buscan la misma clave generen múltiples solicitudes al servicio web por el mismo valor?
Como ya han señalado otros, valueFactory puede invocarse más de una vez. Existe una solución común que mitiga este problema: haga que valueFactory devuelva una instancia de Lazy<T> . Aunque es posible que se creen varias instancias diferidas, el valor T real solo se creará cuando acceda a la propiedad Lazy<T>.Value .
Específicamente:
// Lazy instance may be created multiple times, but only one will actually be used. // GetObjectFromRemoteServer will not be called here. var lazyObject = dict.GetOrAdd("key", key => new Lazy<MyObject>(() => GetObjectFromRemoteServer())); // Only here GetObjectFromRemoteServer() will be called. // The next calls will not go to the server var myObject = lazyObject.Value;Este método se explica con más detalle en la publicación del blog de Reed Copsey
¿Se invoca value factory solo una vez por tecla?
No, no lo es. Los documentos dicen :
Si llama a
GetOrAddsimultáneamente en diferentes subprocesos, valueFactory puede invocarse varias veces , pero es posible que su par clave/valor no se agregue al diccionario para cada llamada.
Echemos un vistazo al código fuente de GetOrAdd :
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) { if (key == null) throw new ArgumentNullException("key"); if (valueFactory == null) throw new ArgumentNullException("valueFactory"); TValue resultingValue; if (TryGetValue(key, out resultingValue)) { return resultingValue; } TryAddInternal(key, valueFactory(key), false, true, out resultingValue); return resultingValue; } Desafortunadamente, en este caso, está claro que nada garantiza que no se llamará a valueFactory más de una vez, si dos invocaciones de GetOrAdd se ejecutan en paralelo.