Problemas de memoria .NET cargando ~ 40 imágenes, memoria no reclamada, potencialmente debido a la fragmentación de LOH

Bueno, esta es mi primera incursión en la creación de perfiles de memoria de una aplicación .NET (ajuste de la CPU que he hecho) y estoy llegando a un poco de un muro aquí.

Tengo una vista en mi aplicación que carga 40 imágenes (máx.) Por página, cada una corriendo aproximadamente ~ 3MB. El número máximo de páginas es 10. Como no quiero mantener 400 imágenes o 1.2GB en la memoria a la vez, configuro cada imagen como nula cuando se cambia la página.

Ahora, al principio pensé que solo debía tener referencias obsoletas a estas imágenes. Descargué ANTS profiler (gran herramienta BTW) y realicé algunas pruebas. El gráfico de duración del objeto me dice que no tengo ninguna referencia a estas imágenes aparte de la referencia única en la clase principal (que, por diseño, también se confirma al peinar minuciosamente mi código):

enter image description here

La clase principal SlideViewModelBase en un caché para siempre, pero la propiedad MacroImage está establecida en nulo cuando se cambia la página. No veo ninguna indicación de que estos objetos se mantengan por más tiempo de lo esperado.

A continuación, eché un vistazo al gran montón de objetos y al uso de la memoria en general. Después de mirar tres páginas de imágenes, tengo 691.9MB de memoria no administrada asignada y 442.3MB en el LOH. System.Byte[] , que viene de mi System.Drawing.Bitmap a BitmapImage conversión está ocupando prácticamente todo el espacio LOH. Aquí está mi código de conversión:

 public static BitmapSource ToBmpSrc( this Bitmap b ) { var bi = new BitmapImage(); var ms = new MemoryStream(); bi.CacheOption = BitmapCacheOption.OnLoad; b.Save( ms, ImageFormat.Bmp ); ms.Position = 0; bi.BeginInit(); ms.Seek( 0, SeekOrigin.Begin ); bi.StreamSource = ms; bi.EndInit(); return bi; } 

Me está costando encontrar dónde va toda esa memoria no administrada. Al principio sospeché de los objetos System.Drawing.Bitmap , pero ANTS no los muestra pegados, y también realicé una prueba en la que me aseguré de que todos fueran eliminados y no hizo la diferencia. Así que aún no he descubierto de dónde viene toda esa memoria no administrada.

Mis dos teorías actuales son:

  1. Fragmentación LOH Si navego fuera de la vista paginada y hago clic en un par de botones, se recupera aproximadamente la mitad de los ~ 1.5GB. Todavía demasiado, pero interesante, no obstante.
  2. Algo extraño de WPF vinculante. Usamos el enlace de datos para mostrar estas imágenes y no soy un experto en lo que respecta a los pormenores de cómo funcionan estos controles WPF.

Si alguien tiene alguna teoría o consejos sobre el perfil, estaría muy agradecido ya que (por supuesto) estamos en un plazo apretado y estoy luchando un poco para terminar esta parte final y funcionando. Creo que me he echado a perder rastreando filtraciones de memoria en C ++ … ¿quién hubiera pensado?

Si necesita más información o desea que intente algo más, pregunte. Perdón por el muro de texto aquí, traté de mantenerlo lo más conciso posible.

Esta publicación de blog parece descifrar lo que está viendo, y la solución propuesta fue crear una implementación de Stream que envuelva otra transmisión .

El método Dispose de esta clase contenedora debe liberar la secuencia envuelta, de modo que pueda ser recolectada como basura. Una vez que la BitmapImage se inicializa con esta secuencia contenedora, la secuencia contenedora puede eliminarse, liberando la secuencia subyacente y permitiendo que se libere la gran matriz de bytes.

BitmapImage guarda una referencia a la secuencia fuente para mantener vivo el objeto MemoryStream. Desafortunadamente, aunque se ha invocado el objeto MemoryStream.Dispose, no libera la matriz de bytes que envuelve la secuencia de la memoria. Entonces, en este caso, el bitmap hace referencia al flujo, que hace referencia al búfer, que puede ocupar mucho espacio en el gran montón de objetos. No hay una verdadera pérdida de memoria; cuando ya no hay más referencias al bitmap, todos estos objetos serán (eventualmente) basura recolectada. Pero dado que el bitmap ya ha hecho su propia copia privada de la imagen (para renderizar), parece bastante inútil tener la copia original ahora innecesaria del bitmap aún en la memoria.

Además, ¿qué versión de .NET estás usando? Antes de .NET 3.5 SP1, había un problema conocido en el que una BitmapImage podía causar una pérdida de memoria . La solución fue llamar a Freeze en BitmapImage.

¿Dónde estás cerrando y eliminando la stream de la memoria? Podría ser que el GC tenga que trabajar mucho más para liberar los recursos moviendo varias generaciones antes de ejecutar los destructores en el objeto (lo que generalmente se llama deshacer si se olvidó de hacerlo).

En su caso, no puede disponer de la secuencia de memoria hasta que haya terminado con la imagen. Cuando desee que se descarguen, recorra las imágenes e intente eliminar la secuencia de la memoria.