2009-11-30 10 views
16

Recientemente me encontré con this VerticalLabel control on CodeProject.¿Es importante desechar SolidBrush y Pen?

Observé que el método OnPaint crea pero no elimina los objetos Pen y SolidBrush.

¿Esto es importante y, en caso afirmativo, cómo puedo demostrar los problemas que pueda causar?

EDITAR

Ésta no es una pregunta sobre el patrón IDisposable en general. Entiendo que las personas que llamen normalmente deberían llamar a Dispose en cualquier clase que implemente IDisposable.

Lo que quiero saber es qué problemas (si hay alguno) pueden esperarse cuando el objeto GDI + no está dispuesto como en el ejemplo anterior. Está claro que, en el ejemplo vinculado, se puede llamar a OnPaint muchas veces antes de que el recolector de basura entre, así que existe el potencial de quedarse sin controladores.

Sin embargo, sospecho que GDI + reutiliza internamente los controladores en algunas circunstancias (por ejemplo, si usa un lápiz de un color específico de la clase Pens, se almacena en caché y se vuelve a utilizar).

Lo que estoy tratando de entender es si un código como ese en el ejemplo vinculado podrá salirse con la negligencia de llamar a Dispose.

Y si no, para ver una muestra que demostró los problemas que puede causar.

Debo agregar que he visto muy a menudo (including the OnPaint documentation on MSDN) ejemplos de código de WinForms que no eliminan los objetos GDI +.

Respuesta

1

Los objetos deben ser eliminados por la recolección de basura después de que han caído fuera de alcance, siempre y cuando no están referenciados por cualquier otra cosa.

La forma más simple de demostrar si esto está sucediendo o no (sin hacer ningún cambio de código) es usar una gran cantidad de estos controles en un formulario y ver si la memoria utilizada por la aplicación sigue creciendo o permanece estable.

+0

entiendo que con el tiempo va a ser GC'd, pero cuando? No esperaría que la memoria creciera, sino la cantidad de controladores que se usan. Y para probar todos los casos, necesitaría estar seguro de crear pinceles/bolígrafos de diferentes colores. – Joe

1

Simplemente está filtrando recursos no administrados (el lápiz/pincel) hasta que el recolector de basura recupera el objeto gestionado correspondiente. Así que desperdicia algunas manijas que ya no se utilizan.

No es una gran cosa porque Finalize() llamará al Dispose(), pero la liberación de recursos no administrados generalmente debe hacerse más temprano que tarde.

+0

Sí, entiendo que el GC eventualmente se activará, pero dado que OnPaint se puede llamar con frecuencia, existe el riesgo de que se desperdicien más de "unos pocos" identificadores. – Joe

+0

Probablemente, sí. Dependiendo de cuánto montón usará .NET y, por lo tanto, depende de la frecuencia con la que se activará el GC.Sin embargo, un método OnPaint probablemente solo genere objetos de generación 0 que serán reclamados con mayor frecuencia. – Joey

+1

Llamarlo "gotear" hace que suene mal. No creo que esté mal en absoluto. –

5

Por supuesto que sí importa. Es importante eliminar todas las instancias de las clases que implementan IDisposable cuando ya no son necesarias. Las clases se definen IDisposable cuando hacen uso de recursos no administrados, es decir, recursos del sistema operativo que no son manejados por .NET Framework.

Si no disponer manualmente los objetos, entonces no serán liberados estos recursos no administrados hasta que el recolector de basura invoca el finalizador objeto (y esto sólo ocurrirá si la clase ha implementado correctamente the Dispose pattern), que puede ser muy tarde ya que el recolector de basura solo se ejecuta cuando detecta una escasez real de memoria. Por lo tanto, cuando no desecha objetos, está reteniendo los recursos del sistema operativo que de otro modo podrían ser utilizados por otras aplicaciones.

Una discusión sobre este tema se pueden encontrar aquí: http://agilology.blogspot.com/2009/01/why-dispose-is-necessary-and-other.html

+4

Mi pregunta no es sobre el patrón IDisposable en general, entiendo para qué sirve y por qué las personas que llaman generalmente deben llamar a Dispose. Ver Editar a la pregunta. – Joe

0

además me gustaría decir que es preferible utilizar las colecciones listas: System.Drawing.Pens. y System.Drawing.Brushes. si no los personalizas de una manera especial.

+0

Sí, estoy de acuerdo. Pero en el ejemplo vinculado, esto no es posible, porque los rotuladores y pinceles solo contienen elementos para una cantidad fija de colores predefinidos. – Joe

2

FWIW, en GDI hay objetos "estándar". Cuando crea un objeto de stock, no tiene que eliminarlo porque es "propiedad" del SO.

Probablemente ya conozca objetos comunes, pero aquí hay un link que entra en detalle.

No sé si hay objetos "en existencia" similares en GDI +. Acabo de buscar brevemente y no he encontrado ninguna referencia a tal.

Como prueba de que escribió un pequeño programa de Windows Forms con una devolución de llamada temporizador (establecido para disparar cada 10 milisegundos) como este:

private void timer1_Tick(object sender, EventArgs e) 
{ 
    byte r = (byte)rnd.Next(0, 256); 
    byte g = (byte)rnd.Next(0, 256); 
    byte b = (byte)rnd.Next(0, 256); 

    System.Drawing.SolidBrush sb = new SolidBrush(Color.FromArgb(0,r,g,b)); 
} 

Si se deja correr, consumirá lentamente la memoria. Al observar TaskManager (no la forma más precisa de medirlo), el uso de la memoria tiende a crecer (en mi máquina, construida con .NET 4.0 VS2010, versión) alrededor de 20k bytes por actualización del Administrador de tareas (a la tasa de actualización más alta). Si llamo a Dispose en el pincel, el uso de memoria tiende a crecer en aproximadamente 8k por actualización de Task Manager.

Difícilmente es una prueba definitiva, pero parece apuntar a un mayor uso de memoria a lo largo del tiempo si SolidBrush no está eliminado. Curiosamente, ni Handles ni Objetos GDI aumentaron en absoluto a medida que se ejecutaba la prueba (en cualquier caso). En base a la experiencia pasada con los recursos de GDI que se filtraron, esperaba ver crecer los Objetos de GDI, particularmente en el caso de no eliminar.

De todos modos, tal vez esto fue informativo, tal vez no.

+0

Mirar el número de identificadores en el administrador de tareas sería el camino a seguir, debería ver cómo se elevaba y bajaba (después del GC) Imaginaba – paulm

1

Necesito arreglar algunas pérdidas de memoria en una aplicación, así que estoy investigando un poco. En pocas palabras, parece que en la mayoría de los casos te salgas con la tuya con un uso de memoria ligeramente mayor.

A continuación se detalla un caso patológico. Superviso mi aplicación de prueba en el administrador de tareas (lo sé crudamente) y observé las columnas Memoria (Conjunto de trabajo privado), Manijas, Objetos del USUARIO y Objetos GDI. Los clics de Button1 causan mayor uso de memoria que los clics de Button2.

Si hago clic rápida y persistentemente en Button1, puedo causar un 'System.OutOfMemoryException' cuando la Memoria alcanza aproximadamente 1.6GB. Button2 nunca supera los 12MB, no importa cuán loca o persistentemente haga clic.

Estoy en una máquina win7 de 64 bits compilando una aplicación de winforms vs. 2010 del perfil de cliente .net 4. Obviamente nunca normalmente tendría construir millones y millones de cepillos ...

Saludos David

private void button1_Click(object sender, EventArgs e) { 
    for (int i = 0; i < 500000; i++) { 
     SolidBrush b = new SolidBrush(Color.FromArgb(2, 32, 43, 128)); 
    }  
} 

private void button2_Click(object sender, EventArgs e) { 
    for (int i = 0; i < 500000; i++) { 
     using (SolidBrush b = new SolidBrush(Color.FromArgb(2, 32, 43, 128))) { 
     } 
    } 
} 
+0

¡Parece implicar que siempre se debe usar! A menos que el compilador elimine por completo la segunda función, ya que podría saber que no hace nada. – paulm

Cuestiones relacionadas