2012-04-09 11 views
8

Voy a crear una GUI que creará dinámicamente conjuntos de controles con eventos asignados. Tendré que agregar y quitar esos controles en tiempo de ejecución. Se parece a esto:precauciones para evitar fugas de memoria debido a controladores de eventos agregados

FlowLayoutPanel.Controls.Clear(); 
<< add new controls, assigning Click events with += >> 

He oído que la asignación de controladores de eventos con + = puede causar pérdidas de memoria (más especificamente, no se liberará la memoria hasta que la aplicación ha salido). Quiero evitar esto. Sé que puedo escribir algunas funciones como aquí How to remove all event handlers from a control para encontrar todos los controladores de eventos y eliminarlos, pero se ve muy complejo.

¿Hay alguna otra manera? ¿Llamar a Dispose ayuda a eliminar esos controladores de eventos? ¿Puedes destruir objetos para forzar que su memoria sea liberada como en C/C++?

Gracias!

PD: El problema es que no sé qué evento desacoplar. Crearé muchas etiquetas y les agregaré diferentes tipos de eventos onclick. Cuando es hora de limpiar el panel de diseño de flujo, no hay forma de saber qué controlador de eventos se adjuntó a qué etiqueta.

Este es el código de ejemplo (_flowLP es un FlowLayoutPanel) - esta función Refresh() se ejecuta varias veces antes de que la aplicación finalice.

private void Refresh() 
    { 
     Label l; 
     Random rnd = new Random(); 

     // What code should i add here to prevent memory leaks 
     _flowLP.Controls.Clear(); 

     l = new Label(); 
     l.Text = "1"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 

     l = new Label(); 
     l.Text = "2"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 

     l = new Label(); 
     l.Text = "3"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 

     l = new Label(); 
     l.Text = "4"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 

     l = new Label(); 
     l.Text = "5"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 

     l = new Label(); 
     l.Text = "6"; 
     if (rnd.Next(3) == 0) l.Click += Method1; 
     if (rnd.Next(3) == 0) l.Click += Method2; 
     if (rnd.Next(3) == 0) l.Click += Method3; 
     _flowLP.Controls.Add(l); 
    } 
+0

En cuanto a su edición: si los métodos añadidos a la lista de invocación de un evento no se pueden determinar con certeza, puede mantener los delegados que se han agregado a los manejadores de eventos en una colección en algún lugar y usar eso para eliminar los delegados cuando proviene. O bien, si sabe que está borrando por completo las listas de invocación de los eventos, simplemente borre todo. – phoog

Respuesta

3

Esto principalmente va a ser una preocupación cuando se conecta un consumidor de eventos de vida más corta a un productor de eventos de mayor duración. Si tienen vidas similares o son lo opuesto a lo que describí, no es un problema.

En caso de que no se preocupe por esto, simplemente use - = para desconectarse del evento. Eso elimina la referencia creada por el archivo adjunto y ayuda a evitar este tipo de problema de memoria.

Editar: Dado que los comentarios son un poco largos, voy a publicar un seguimiento aquí. Cuando te apegas a un evento, lo que estás haciendo es colgar una referencia a ti mismo en el proveedor del evento. Entonces, por ejemplo, si tuviste una clase Clock con un evento StrikesMidnight y te suscribes a ese evento de una clase llamada Bedtime, la mecánica actual de Bedtime diciendo clock.StrikesMidnight += this.HandleMidnight; es que estás asignando una referencia al reloj a ti mismo. Es como si el reloj tuviera una propiedad de objeto y dijera clock.ObjectProperty = this;

Por lo tanto, en un caso donde la clase de hora de acostarse es efímera y queda fuera de alcance, Bedtime aparece, cuelga una referencia en Clock y luego se apaga fuera del ámbito. El problema es que Clock todavía tiene una referencia, por lo que, aunque esté fuera del alcance, Garbage Collector no recopilará Bedtime.

....

Ese es el fondo. En su caso, está creando una etiqueta y adjuntando una referencia suya (a través de sus manejadores "MethodX"). Cuando se invoca la actualización, borra su lista de etiquetas (lo que significa que están fuera del alcance). Van fuera de alcance y tienen referencias a su clase a través de sus manejadores MethodX, pero ¿y qué? El hecho de que tengan referencias no impide que reciban GC. Nadie contiene referencias a ellos en su código, por lo que el GC hará su trabajo con ellos y no perderá memoria.

+1

¡Pero no sé qué evento separar! ¿Qué hago entonces? – Istrebitel

+1

Es solo el inverso de lo que estás haciendo con tus instrucciones + =. Cada vez que conecte un controlador de evento local al evento de algún colaborador, recuerde desconectarse con - = cuando ya no necesite estar al tanto del evento que se está levantando. Puede hacer esto en un método de eliminación() si lo desea, aunque eso no es estrictamente necesario. –

+0

sí, pero no sé qué método separar. Por ejemplo, tengo 10 métodos, uno de los cuales se adjuntó a la etiqueta 5 de 20 hace un tiempo basado en las condiciones en ese momento. Ahora necesito eliminar todas las etiquetas del panel de salida de flujo. ¿Cómo sé cuál de los 10 manejadores de eventos tengo que - =? – Istrebitel

0

Todos los controles deben ser limpiados por el recolector de basura siempre que elimine el formulario que lo contiene.

La suscripción al evento en los controles no mantendrá el control activo, porque el control tiene una referencia al delegado de manejo; el delegado no tiene una referencia al control.

La situación en la que una suscripción de evento evitará que un control se limpie, es cuando algún código en el control se suscribe a un evento fuera de la instancia de formulario en la que está contenido. Por ejemplo, si un cuadro combinado personalizado se suscribe a un evento en una clase estática para informar al control cuándo se debe actualizar su lista de opciones. Si el control personalizado no deshace este evento, el registro de eventos de clases estáticas hará referencia a él durante la aplicación. Como el control tendrá una referencia a su contenedor (y así sucesivamente), la forma completa probablemente permanecerá en la memoria. En casos como este, el control debería desenvolver el evento en su método Dispose. La suscripción a eventos en clases estáticas o clases de instancias de larga duración siempre debe generar una bandera roja y debe estar explícitamente desactivado cuando ya no sea necesario.

En los formularios, siempre que todos sus eventos estén conectados a métodos de instancia en la clase de formulario o en objetos cuyo ámbito está controlado por la instancia de formulario, los controles se limpiarán cuando el gráfico de objeto que la forma es rooting está fuera del alcance. Solo tenga cuidado de que las clases externas no tengan una referencia al formulario después de que ya no se necesite.

+0

En otras palabras, si un objeto que sale del alcance tiene el método de otro objeto subscrito a su evento, será descartado y GCed bien, pero si un objeto que sale del alcance tiene algunos de sus métodos suscritos a algún otro evento del objeto, entonces ¿no será GCed? – Istrebitel

+0

Eso es bastante correcto. Igual obtendrán basura recolectada si el objeto al que se suscriben los eventos se vuelve elegible para la recolección. Las referencias circulares entre objetos elegibles para ser recogidos no impedirán que se recopilen. –

0

Mi primera sugerencia sería no pre optimizar. Asegúrese de que esto sea un problema antes de intentar resolverlo.

Con respecto a Dispose(): Dispose debe utilizarse ONLY para liberar recursos no administrados antes de que se recoja un objeto. Ver http://msdn.microsoft.com/en-us/library/system.idisposable.aspx para más información.

Para evitar que los controladores de eventos se aferren a las referencias a los controles, necesitarás tener tus controles anulados de todos los controladores de eventos a los que se suscriben cuando se crean. SUGERENCIA: Si está agregando controladores de eventos a través de la GUI, investigue el código generado automáticamente para ver de qué necesita cancelar la suscripción antes de llamar al FlowLayoutPanel.Controls.Clear(). Una manera limpia (¿más?) De hacer esto sería crear su propio control que hereda del control deseado y agregar un método `Cleanup() 'que cancela la suscripción de cualquier controlador de eventos al que se haya suscrito (debe saber qué evento los manejadores se han suscrito, ya sea porque usted escribió el código o porque se generó para usted).

+0

No, como he dicho, estoy haciendo esto de forma dinámica, por lo que no a través de GUI. – Istrebitel

Cuestiones relacionadas