.net - leak - Prevención de fugas de memoria con comportamientos adjuntos
memory leak java (11)
Bueno, eso (el gerente poco) sin duda puedo entender, y simpatizar con.
Pero como sea que Microsoft lo llame, no creo que una definición "nueva" sea apropiada. Es complicado, porque no vivimos en un mundo 100% administrado (aunque a Microsoft le gusta pretender que sí, Microsoft no vive en un mundo así). Cuando dice pérdida de memoria, podría querer decir que un programa consume demasiada memoria (esa es la definición de un usuario) o que una referencia administrada no se liberará hasta la salida (como aquí) o que una referencia no administrada no se limpia correctamente hasta (que sería una pérdida de memoria real), o que el código no administrado llamado desde el código administrado está perdiendo memoria (otra fuga real).
En este caso, es obvio lo que significa "pérdida de memoria", a pesar de que estamos siendo imprecisos. Pero es tedioso hablar con algunas personas, que llaman a todos los excesos de consumo o a la falta de recolección de una pérdida de memoria; y es frustrante cuando estas personas son programadores, quienes supuestamente saben mejor. Es algo importante que los términos técnicos tengan significados inequívocos, creo. La depuración es mucho más fácil cuando lo hacen.
De todas formas. No significa convertir esto en una discusión al aire libre sobre el lenguaje. Solo digo...
Creé un "comportamiento adjunto" en mi aplicación WPF que me permite manejar la tecla Enter y pasar al siguiente control. Lo llamo EnterKeyTraversal.IsEnabled, y puedes ver el código en mi blog aquí .
Mi principal preocupación ahora es que puedo tener una pérdida de memoria, ya que estoy manejando el evento PreviewKeyDown en UIElements y nunca explícitamente "desenganchar" el evento.
¿Cuál es el mejor enfoque para evitar esta fuga (si es que hay una)? ¿Debo mantener una lista de los elementos que estoy administrando y desenganchar el evento PreviewKeyDown en el evento Application.Exit? ¿Alguien ha tenido éxito con los comportamientos adjuntos en sus propias aplicaciones WPF y ha llegado a una solución elegante de gestión de memoria?
Verdad verdad,
Tienes razón, por supuesto ... Pero hay una nueva generación de programadores que nacen en este mundo que nunca tocarán el código no administrado, y creo que las definiciones de lenguaje se reinventarán una y otra vez. Las fugas de memoria en WPF son de este modo diferentes a, por ejemplo, C / Cpp.
O, por supuesto, a mis gerentes me referí a él como un error de memoria ... ¡a mis colegas colegas lo llamé un problema de rendimiento!
En referencia al problema de Matt, podría ser un problema de rendimiento que podría necesitar abordar. Si solo usa algunas pantallas y hace que esas pantallas controlen un solo tono, es posible que no vea este problema en absoluto;).
¿Has pensado en implementar el "Patrón de eventos débiles" en lugar de eventos regulares?
@Arcturus:
... obstruye tu memoria y hace que tu aplicación sea realmente lenta cuando no se recogen basura.
Eso es cegadoramente obvio, y no estoy en desacuerdo. Sin embargo:
... estás perdiendo memoria para objetar que ya no usas ... porque hay una referencia a ellos.
"la memoria se asigna a un programa, y ese programa posteriormente pierde la capacidad de acceder a él debido a fallas en la lógica del programa" (Wikipedia, "fuga de memoria")
Si hay una referencia activa a un objeto, al que su programa puede acceder, entonces, por definición, no está perdiendo memoria. Una fuga significa que el objeto ya no es accesible (para usted o para el OS / Framework) y no se liberará durante la vigencia de la sesión actual del sistema operativo . Este no es el caso aquí.
(Perdón por ser un nazi semántico ... tal vez soy un poco viejo en la escuela, pero la filtración tiene un significado muy específico. Las personas tienden a usar "pérdida de memoria" en estos días para significar cualquier cosa que consuma 2KB de memoria más de lo que desean. ..)
Pero, por supuesto, si no libera un controlador de eventos, el objeto al que está conectado no se liberará hasta que el recolector de basura recupere la memoria de proceso al momento del cierre. Pero este comportamiento es completamente esperado, al contrario de lo que parece implicar. Si espera recuperar un objeto, debe eliminar todo lo que pueda mantener viva la referencia, incluidos los manejadores de eventos.
@Nick Sí, la cosa con comportamientos adjuntos es que, por definición, no están en el mismo objeto que los elementos cuyos eventos está manejando.
Creo que la respuesta está dentro de usar WeakReference de alguna manera, pero no he visto ejemplos de código simples para explicarme. :)
Acabo de leer la publicación de tu blog y creo que recibiste un consejo engañoso, Matt. Si hay una pérdida de memoria real aquí, entonces eso es un error en .NET Framework, y no es algo que pueda corregir necesariamente en su código.
Lo que creo que usted (y el póster de su blog) están hablando aquí no es en realidad una fuga, sino más bien un consumo continuo de memoria. Eso no es lo mismo. Para ser claros, la memoria filtrada es memoria que está reservada por un programa, luego abandonada (es decir, un puntero se deja colgando) y que posteriormente no puede liberarse. Dado que la memoria se gestiona en .NET, esto es teóricamente imposible. Sin embargo, es posible que un programa reserve una cantidad de memoria cada vez mayor sin permitir que las referencias salgan del alcance (y sean elegibles para la recolección de basura); sin embargo, esa memoria no se ha filtrado. El GC lo devolverá al sistema una vez que su programa finalice.
Asi que. Para responder a su pregunta, no creo que realmente tenga un problema aquí. Ciertamente no tiene una pérdida de memoria, y desde su código, no creo que deba preocuparse, en lo que respecta al consumo de memoria. Siempre que se asegure de no asignar repetidamente ese controlador de eventos sin haberlo desasignado (es decir, que solo lo configure una vez o que lo elimine exactamente una vez por cada vez que lo asigne), usted parece estar haciendo, su código debería estar bien.
Parece que ese es el consejo que el afiche de tu blog intentaba darte, pero usó ese alarmante trabajo "fuga", que es una palabra aterradora, pero que muchos programadores han olvidado del significado real en el mundo administrado; no aplica aquí.
Asegúrese de que los elementos de referencia de evento estén en el objeto al que hacen referencia, como cuadros de texto en el control de formulario. O si eso no se puede prevenir. Cree un evento estático en una clase de ayuda global y luego supervise la clase de ayuda global para los eventos. Si no se pueden hacer estos dos pasos, intente utilizar WeakReference, por lo general son perfectos para estas situaciones, pero vienen con gastos generales.
Dejando a un lado el debate filosófico, al mirar la publicación de blog de OP, no veo ninguna filtración aquí:
ue.PreviewKeyDown += ue_PreviewKeyDown;
Una referencia difícil a ue_PreviewKeyDown
se almacena en ue.PreviewKeyDown
.
ue_PreviewKeyDown
es un método STATIC
y no puede ser GCed
.
No se almacena ninguna referencia dura a ue
, por lo que nada impide que se GCed
como GCed
.
Entonces ... ¿Dónde está la fuga?
Para explicar mi comentario sobre la publicación de John Fenton aquí está mi respuesta. Veamos el siguiente ejemplo:
class Program
{
static void Main(string[] args)
{
var a = new A();
var b = new B();
a.Clicked += b.HandleClicked;
//a.Clicked += B.StaticHandleClicked;
//A.StaticClicked += b.HandleClicked;
var weakA = new WeakReference(a);
var weakB = new WeakReference(b);
a = null;
//b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("a is alive: " + weakA.IsAlive);
Console.WriteLine("b is alive: " + weakB.IsAlive);
Console.ReadKey();
}
}
class A
{
public event EventHandler Clicked;
public static event EventHandler StaticClicked;
}
class B
{
public void HandleClicked(object sender, EventArgs e)
{
}
public static void StaticHandleClicked(object sender, EventArgs e)
{
}
}
Si usted tiene
a.Clicked += b.HandleClicked;
y establecer solo b para anular ambas referencias weakA y weakB ¡permanecer vivo! Si configura solo a a null b permanece activo pero no a (lo que prueba que John Fenton está equivocado al indicar que se almacena una referencia dura en el proveedor del evento, en este caso a).
Esto me llevó a la conclusión INCORRECTA de que
a.Clicked += B.StaticHandleClicked;
daría lugar a una fuga porque pensé que el controlador estático mantendría la instancia de a. Este no es el caso (prueba mi programa). En el caso de controlador de eventos estáticos o eventos, es al revés. Si tú escribes
A.StaticClicked += b.HandleClicked;
se mantendrá una referencia a b.
No estoy de acuerdo DannySmurf
Algunos objetos de diseño WPF pueden obstruir su memoria y hacer que su aplicación realmente lenta cuando no se recogen basura. Por lo tanto, creo que la elección de las palabras es correcta, está filtrando memoria a objetos que ya no usa. Espera que los elementos sean basura, pero no lo son, porque hay una referencia en alguna parte (en este caso en el controlador de eventos).
Ahora para una respuesta real :)
Le aconsejo que lea este artículo de WPF Performance en MSDN
No eliminar controladores de eventos en objetos puede mantener vivos los objetos
El delegado que un objeto pasa a su evento es efectivamente una referencia a ese objeto. Por lo tanto, los controladores de eventos pueden mantener los objetos vivos más de lo esperado. Al realizar la limpieza de un objeto que se ha registrado para escuchar el evento de un objeto, es esencial eliminar ese delegado antes de liberar el objeto. Mantener vivos los objetos innecesarios aumenta el uso de la memoria de la aplicación. Esto es especialmente cierto cuando el objeto es la raíz de un árbol lógico o un árbol visual.
Te aconsejan mirar el patrón Evento Débil
Otra solución sería eliminar los controladores de eventos cuando haya terminado con un objeto. Pero sé que con las propiedades adjuntas ese punto podría no estar siempre claro ...
¡Espero que esto ayude!
Sí, sé que en los viejos tiempos Memory Leaks es un tema completamente diferente. Pero con el código administrado, el nuevo significado del término Memory Leak podría ser más apropiado ...
Microsoft incluso reconoce que es una pérdida de memoria:
¿Por qué implementar el patrón WeakEvent?
Escuchar eventos puede provocar pérdidas de memoria. La técnica típica para escuchar un evento es usar la sintaxis específica del idioma que conecta un controlador a un evento en una fuente. Por ejemplo, en C #, esa sintaxis es: source.SomeEvent + = new SomeEventHandler (MyEventHandler).
Esta técnica crea una fuerte referencia desde el origen del evento hasta el oyente del evento. Normalmente, asociar un controlador de eventos a un oyente hace que el oyente tenga un tiempo de vida de los objetos influenciado por la duración del objeto para el origen (a menos que el controlador de eventos se elimine explícitamente). Pero en determinadas circunstancias, es posible que desee que el tiempo de duración del objeto del oyente esté controlado únicamente por otros factores, como si actualmente pertenece al árbol visual de la aplicación y no a la duración de la fuente. Siempre que el tiempo de vida del objeto fuente se extiende más allá de la duración del objeto del oyente, el patrón de evento normal lleva a una pérdida de memoria: el oyente se mantiene activo más de lo previsto.
Usamos WPF para una aplicación de cliente con ToolWindows grandes que se pueden arrastrar, todas las cosas ingeniosas y todas compatibles en un XBAP ... Pero tuvimos el mismo problema con algunas ToolWindows que no eran basura. Esto se debía al hecho de que todavía dependía de los oyentes del evento ... Ahora, esto podría no ser un problema cuando cierre la ventana y cierre su aplicación. Pero si está creando ToolWindows muy grandes con una gran cantidad de comandos, y todos estos comandos se vuelven a evaluar una y otra vez, y la gente debe usar su aplicación todo el día ... Te puedo decir ... realmente obstruye tu memoria y tiempo de respuesta de su aplicación.
Además, me resulta mucho más fácil explicarle a mi gerente que tenemos una pérdida de memoria, que explicarle que algunos objetos no son basura recolectada debido a algunos eventos que necesitan limpieza;)