ASP.NET: Accesos simultáneos a un diccionario pueden causar problemas de rendimiento

He tenido varios
casos en los que una aplicación ASP.NET se iba volviendo cada vez más lenta hasta que al final dejaba de dar servicio.

A priori es difícil determinar la causa, por lo que la mejor forma de actuación ha sido la de capturar volcados de memoria cuando se está dando esa situación. Tras analizar los volcados pude comprobar que se trataba de un problema con el acceso a un diccionario (System.Collections.Generic.Dictionary).

Uno de los hilos tenía este aspecto:

2246e8c4 792f522b System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon)

2246e8e4 20d0a081 Aplicacion.Datos.RecuperarDatos()

2246eb98 20d0115c CargaPagina.aspx

 

Y el del otro hilo es:

2253ddac 792f28f4 System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].Insert(System.__Canon, System.__Canon, Boolean)

2253e8e4 792f28f4 Aplicacion.Datos.RecuperarDatos()

2253eb98 792f28f4 CargaPagina.aspx

 

Como podéis ver, los dos hilos están invocando a una función cualquiera que opera con un diccionario, Aplicacion.Datos.RecuperarDatos(). Uno de los hilos está leyendo (FindEntry) y el otro está escribiendo (Insert)

Ahí tenemos el problema, porque un diccionario puede ser accedido por varios hilos siempre y cuando no sea modificado. En este artículo https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.80).aspx, se describe este comportamiento:

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Parece ser que los hilos se están bloqueando el uno al otro cuando uno ejecuta  FindEntry y el otro está modificando con un Insert. Por lo que para resolver este tipo de situaciones debemos controlar que nuestro código no permita que se pueda leer de un diccionario cuando se están realizando modificaciones sobre el mismo.

 

Espero que esta información os sea de utilidad

- José Ortega Gutiérrez