c# .net memory-leaks event-handling windbg

c# - Por qué esto no causa una pérdida de memoria cuando el evento no se anula



.net memory-leaks (2)

Estoy tratando de entender cómo los eventos pueden causar una pérdida de memoria. Encontré una buena explicación para esta pregunta de stackoverflow, pero cuando veo objetos en Windg, me confundo con el resultado. Para empezar, tengo una clase simple de la siguiente manera.

class Person { public string LastName { get; set; } public string FirstName { get; set; } public event EventHandler UponWakingUp; public Person() { } public void Wakeup() { Console.WriteLine("Waking up"); if (UponWakingUp != null) UponWakingUp(null, EventArgs.Empty); } }

Ahora estoy usando esta clase en una aplicación de formulario de Windows de la siguiente manera.

public partial class Form1 : Form { Person John = new Person() { LastName = "Doe", FirstName = "John" }; public Form1() { InitializeComponent(); John.UponWakingUp += new EventHandler(John_UponWakingUp); } void John_UponWakingUp(object sender, EventArgs e) { Console.WriteLine("John is waking up"); } private void button1_Click(object sender, EventArgs e) { John = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); MessageBox.Show("done"); } }

Como puede ver, instalé la clase Person y me suscribí al evento UponWakingUp. Tengo un botón en este formulario. Cuando el usuario hace clic en este botón, configuro esta instancia de persona como nula sin anular la suscripción al evento. Luego llamo a GC.Collect para asegurarme de que se haya realizado la recolección de Garbade. Estoy mostrando un cuadro de mensaje aquí para poder adjuntar Windbg para buscar referencias de ayuda en la clase Form1 y dentro de esta clase no veo ninguna referencia a ese evento (la salida de Windbg se muestra a continuación aunque Form1 tiene datos demasiado largos, estoy mostrando relacionado a mi pregunta). Esta clase tiene una referencia a la clase Persona, pero es nula. Básicamente, esto no parece una pérdida de memoria ya que Form1 no tiene ninguna referencia a la clase Person, incluso si no se canceló la suscripción al evento.

Mi pregunta es si esto causa una pérdida de memoria. ¿Si no, porque no?

0:005> !do 0158d334 Name: WindowsFormsApplication1.Form1 MethodTable: 00366390 EEClass: 00361718 Size: 332(0x14c) bytes File: c:/Sandbox//WindowsFormsApplication1/WindowsFormsApplication1/bin/Debug/WindowsFormsApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 619af744 40001e0 4 System.Object 0 instance 00000000 __identity 60fc6c58 40002c3 8 ...ponentModel.ISite 0 instance 00000000 site 619af744 4001534 b80 System.Object 0 static 0158dad0 EVENT_MAXIMIZEDBOUNDSCHANGED **00366b70 4000001 13c ...plication1.Person 0 instance 00000000 John** 60fc6c10 4000002 140 ...tModel.IContainer 0 instance 00000000 components 6039aadc 4000003 144 ...dows.Forms.Button 0 instance 015ad06c button1 0:008> !DumpHeap -mt 00366b70 Address MT Size total 0 objects Statistics: MT Count TotalSize Class Name Total 0 objects


Este es un caso de referencia circular . El formulario tiene una referencia al objeto que escucha el evento a través del campo John . A su vez, John tiene una referencia al formulario cuando el evento UponWakingUp fue suscrito por el constructor del formulario.

Las referencias circulares pueden ser un problema en ciertos esquemas de gestión automática de memoria, particularmente en el recuento de referencias. Pero el recolector de basura .NET no tiene un problema con. Siempre que ni el objeto de formulario ni el objeto de persona tengan referencias adicionales, la referencia circular entre los dos no puede mantenerse viva.

No hay referencias adicionales a ninguno en tu código. Lo que normalmente haría que ambos objetos sean basura. Pero la clase Form es especial, siempre y cuando exista una ventana nativa de Windows, una referencia interna almacenada en una tabla handle-to-object mantenida por Winforms mantiene vivo el objeto de formulario. Lo que mantiene a John vivo.

Entonces, la forma normal de limpiar esto es que el usuario cierra la ventana haciendo clic en la X en la esquina superior derecha. Que a su vez hace que se destruya el identificador nativo de la ventana. Lo cual elimina la referencia del formulario de esa tabla interna. La próxima recolección de basura ahora no ve más que la referencia circular y los recopila a ambos.


La respuesta está en la respuesta a la pregunta a la que se ha vinculado:

Cuando un oyente conecta un oyente de evento a un evento, el objeto fuente obtendrá una referencia al objeto oyente. Esto significa que el recolector de elementos no utilizados no puede recopilar el elemento de escucha hasta que el controlador de eventos se separe o se recopile el objeto de origen .

Está liberando el objeto fuente ( Person ) para que el Oyente (su Form ) pueda ser recolectado y por eso no hay pérdida de memoria.

Se producirá una pérdida de memoria cuando esta situación sea inversa a la de IE cuando desee deshacerse del Form pero el origen del evento (su objeto Person ) todavía está vivo con una referencia al mismo.