2012-05-07 7 views
31

La documentación de ConcurrentDictionary estado no hace explícita, así que supongo que no podemos esperar que los delegados valueFactory y updateValueFactory tienen su ejecución sincronizada (de GetOrAdd() y AddOrUpdate() las operaciones, respectivamente).ConcurrentDictionary Pitfall - ¿Están delegadas las fábricas de GetOrAdd y AddOrUpdate sincronizadas?

Por lo tanto, creo que no podemos implementar el uso de recursos dentro de ellos que necesitan control concurrente sin implementar manualmente nuestro propio control simultáneo, tal vez simplemente usando [MethodImpl(MethodImplOptions.Synchronized)] sobre los delegados.

¿Estoy en lo cierto? O el hecho de que ConcurrentDictionary es seguro para subprocesos, podemos esperar que las llamadas a estos delegados se sincronicen automáticamente (también a prueba de subprocesos)?

Respuesta

33

Sí, tiene razón, los delegados de los usuarios no están sincronizados por ConcurrentDictionary. Si necesita sincronizarlos, es su responsabilidad.

El MSDN mismo dice:

Además, aunque todos los métodos de ConcurrentDictionary son compatibles con el proceso, no todos los métodos son atómicas, específicamente GetOrAdd y AddOrUpdate. El delegado de usuario que se pasa a estos métodos es invocado fuera del bloqueo interno del diccionario. (Esto se hace para evitar que el código desconocido de bloquear todas las discusiones.)

See "How to: Add and Remove Items from a ConcurrentDictionary

Esto es porque el ConcurrentDictionary tiene ni idea de lo que el delegado que usted provee hacer o su rendimiento, por lo que si se trató de bloqueo a su alrededor, realmente podría afectar negativamente el rendimiento y arruinar el valor de ConcurrentDictionary.

Por lo tanto, es responsabilidad del usuario sincronizar su delegado si es necesario. El enlace de MSDN anterior en realidad tiene un buen ejemplo de las garantías que hace y no hace.

+1

Así que estoy envolviendo la llamada al método GetOrAdd en un bloqueo, pero eso hace que el propósito del ConcurrentDictionary sea inútil. ¿Hay una mejor manera? – John

+0

Estoy un poco confundido por esto, ¿básicamente estamos diciendo que si se pasa un delegado como clave, su ejecución no se sincronizará al evaluar el * valor *? O si tenemos un diccionario con el tipo "delegar" como el valor ... que cuando intentamos ejecutar el delegado (es decir, 'diccionario [clave] (argumento);') que la ejecución no se sincronizará? O, ¿me estoy perdiendo el punto por completo? Gracias. – Snoopy

+0

@Snoopy: el delegado que evalúa el valor para agregar no está sincronizado. Esto significa que si su delegado hace algo que no es seguro para subprocesos * y * no intentó sincronizar esa operación usted mismo, entonces sucederán cosas malas. Pero, lo que un delegado que actúa como fábrica de un valor normalmente no estaría haciendo cosas que no son seguras para subprocesos por naturaleza. La mayoría de las veces, el delegado probablemente solo está haciendo 'nuevo SomeObject()', que definitivamente es seguro para subprocesos porque es una operación sin estado. –

22

No solo estos delegados no están sincronizados, sino que ni siquiera se garantiza que sucedan solo una vez. De hecho, pueden ejecutarse varias veces por llamada al AddOrUpdate.

Por ejemplo, el algoritmo para AddOrUpdate se parece a esto.

TValue value; 
do 
{ 
    if (!TryGetValue(...)) 
    { 
    value = addValueFactory(key); 
    if (!TryAddInternal(...)) 
    { 
     continue; 
    } 
    return value; 
    } 
    value = updateValueFactory(key); 
} 
while (!TryUpdate(...)) 
return value; 

Tenga en cuenta dos cosas aquí.

  • No hay esfuerzo para sincronizar la ejecución de los delegados.
  • Los delegados se pueden ejecutar más de una vez desde que se invocaron dentro de el bucle.

Así que debe asegurarse de hacer dos cosas.

  • Proporcione su propia sincronización para los delegados.
  • Asegúrese de que sus delegados no tengan ningún efecto secundario que dependa del número de veces que se ejecutan.
+4

@Luciano: Acabo de verificar y 'GetOrAdd' parece llamar a' ValueFactory' una sola vez. Por lo tanto, solo 'AddOrUpdate' tiene el potencial de llamar a los delegados varias veces. Eso puede ser un error de parte de Microsoft ... no estoy seguro. Descubrí esto durante mucho tiempo [respondiendo una pregunta similar] (http://stackoverflow.com/a/3831892/158779). –

+0

@BrianGideon: por supuesto, lo siento, no me he dado cuenta de que estaba hablando de AddOrUpdate en lugar de GetOrAdd. Gracias por responder. – Luciano

+0

¿Ha cambiado este comportamiento de alguna manera hasta ahora? – Sebastian

Cuestiones relacionadas