threading thread programming parallel net for c# .net multithreading parallel-processing

c# - thread - task parallel



¿Se garantiza que ConcurrentDictionary.GetOrAdd() invoque valueFactoryMethod solo una vez por clave? (3)

¿Se invoca valor de fábrica solo una vez por clave?

No, no lo es. GetOrAdd() :

Si llama a GetOrAdd simultáneamente en diferentes subprocesos, puede invocarse a valueFactory varias veces, varias veces , pero es posible que su par clave / valor no se agregue al diccionario para cada llamada.

Problema: Necesito implementar caché de objetos. El caché debe ser seguro para subprocesos y debe rellenar los valores a pedido (carga diferida). Los valores se recuperan a través del servicio web mediante Clave (funcionamiento lento). Así que decidí usar ConcurrentDictionary y su método GetOrAdd() que tiene un método de fábrica de valores que supone que la operación es atómica y está sincronizada. Desafortunadamente, encontré la siguiente declaración en el artículo de MSDN: Cómo agregar y eliminar elementos de un ConcurrentDictionary :

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 todavía no responde mi respuesta completamente.

Pregunta: ¿Se invoca valor de fábrica solo una vez por clave? En mi caso específico: ¿Es posible que varios subprocesos que buscan la misma clave generen una solicitud múltiple al servicio web por el mismo valor?


Como otros ya han señalado, valueFactory puede invocarse más de una vez. Existe una solución común que mitiga este problema: valueFactory su valueFactory devuelva una instancia Lazy<T> . Aunque es posible que se creen varias instancias perezosas, el valor T real solo se creará cuando Lazy<T>.Value 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.


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 GetOrAdd ejecutan en paralelo.