17

este es un ejemplo construido. No quiero publicar el código original aquí. Intenté extraer las partes relevantes sin embargo.Interfaces, métodos anónimos y fugas de memoria

Tengo una interfaz que administra una lista de oyentes.

TListenerProc = reference to procedure (SomeInt : ISomeInterface); 

ISomeInterface = interface 
    procedure AddListener (Proc : TListenerProc); 
end; 

Ahora puedo registrar un oyente:

SomeObj.AddListener (MyListener); 

procedure MyListener (SomeInt : ISomeInterface); 
begin 
    ExecuteSynchronized (procedure 
         begin 
         DoSomething (SomeInt); 
         end); 
end; 

me pongo pérdidas de memoria. Tanto el método anónimo como las interfaces nunca se liberan. Sospecho que esto se debe a algún tipo de referencia circular aquí. El método anónimo mantiene viva la interfaz y la interfaz mantiene vivo el método anónimo.

dos preguntas:

  1. ¿Apoya que la explicación? ¿O me estoy perdiendo algo más aquí?
  2. ¿Hay algo que pueda hacer al respecto?

¡Gracias de antemano!


EDITAR: No es tan fácil de reproducir esto en una aplicación suficientemente pequeño como para publicar aquí. Lo mejor que puedo hacer ahora es lo siguiente. El método anónimo no quede liberado aquí:

program TestMemLeak; 

{$APPTYPE CONSOLE} 

uses 
    Generics.Collections, SysUtils; 

type 
    ISomeInterface = interface; 
    TListenerProc = reference to procedure (SomeInt : ISomeInterface); 

    ISomeInterface = interface 
    ['{DB5A336B-3F79-4059-8933-27699203D1B6}'] 
    procedure AddListener (Proc : TListenerProc); 
    procedure NotifyListeners; 
    procedure Test; 
    end; 

    TSomeInterface = class (TInterfacedObject, ISomeInterface) 
    strict private 
    FListeners   : TList <TListenerProc>; 
    protected 
    procedure AddListener (Proc : TListenerProc); 
    procedure NotifyListeners; 
    procedure Test; 
    public 
    constructor Create; 
    destructor Destroy; override; 
    end; 


procedure TSomeInterface.AddListener(Proc: TListenerProc); 
begin 
FListeners.Add (Proc); 
end; 

constructor TSomeInterface.Create; 
begin 
FListeners := TList <TListenerProc>.Create; 
end; 

destructor TSomeInterface.Destroy; 
begin 
FreeAndNil (FListeners); 
    inherited; 
end; 

procedure TSomeInterface.NotifyListeners; 

var 
    Listener : TListenerProc; 

begin 
for Listener in FListeners do 
    Listener (Self); 
end; 

procedure TSomeInterface.Test; 
begin 
// do nothing 
end; 

procedure Execute (Proc : TProc); 

begin 
Proc; 
end; 

procedure MyListener (SomeInt : ISomeInterface); 
begin 
Execute (procedure 
     begin 
     SomeInt.Test; 
     end); 
end; 

var 
    Obj  : ISomeInterface; 

begin 
    try 
    ReportMemoryLeaksOnShutdown := True; 
    Obj := TSomeInterface.Create; 
    Obj.AddListener (MyListener); 
    Obj.NotifyListeners; 
    Obj := nil; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 
+0

Debería mostrarnos cómo funciona AddListener. –

+0

Acabo de ponerlos en un 'TList .' – jpfollenius

+1

Todo el código que veo se ve bien. El problema debe estar en la parte oculta. ¿Puedes mostrar un ejemplo completo que produce la fuga? –

Respuesta

8

Su código está lejos de ser mínimo. El siguiente:

program AnonymousMemLeak; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils; 

type 
    TListenerProc = reference to procedure (SomeInt : IInterface); 

procedure MyListener (SomeInt : IInterface); 
begin 
end; 

var 
    Listener: TListenerProc; 

begin 
    try 
    ReportMemoryLeaksOnShutdown := True; 

    Listener := MyListener; 
    Listener := nil; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 

tiene el mismo problema (Delphi 2009 aquí). Esto no se puede trabajar o diseñar alrededor. Me parece un error en el compilador.

Editar:

O tal vez este es un problema de la detección de fugas de memoria. No tiene nada que ver con que el parámetro sea una interfaz, un procedimiento sin parámetros conduce a la misma "fuga". Muy extraño.

+0

Eso parece ser un problema de compilación. Si mueve el código (el bloque try) de la rutina principal del programa a un procedimiento y luego hace que la llamada principal sea el procedimiento, no se informa ninguna fuga. –

+0

De acuerdo, sry para que el código de muestra sea demasiado largo. Pensé que el conteo de referencias de interfaz debe estar involucrado. Parece que este no es el caso. – jpfollenius

+0

y +1 para extraer la esencia de este problema. – jpfollenius

3

me parece una cuestión referencia circular definida. Los métodos anónimos se gestionan a través de interfaces ocultas, y si el objeto en el que se implementa ISomeInterface es el TList<TListenerProc>, entonces tiene un problema de referencia circular.

Una posible solución sería poner un método ClearListeners en ISomeInterface que llama a .Clear en el TList<TListenerProc>. Mientras nada más haga referencia a los métodos anónimos, eso los haría desaparecer y dejar caer sus referencias a ISomeInterface.

He hecho algunos artículos sobre la estructura y la implementación de métodos anónimos que pueden ayudarlo a comprender con qué está realmente trabajando y cómo funcionan un poco mejor. Puede encontrarlos en http://tech.turbu-rpg.com/category/delphi/anonymous-methods.

+0

¿Y dónde llamar al método 'ClearListeners'? – jpfollenius

+0

Lo siento si esta es una respuesta algo genérica, pero "durante la limpieza". Cuando quiera que todo esto salga del alcance. –

+0

¡Gracias hasta ahora, Mason! ¿Podría echar un vistazo a la pregunta editada y el código de ejemplo? Cuando puse la llamada a 'ClearListeners' al final del método principal, el método anónimo todavía se filtró. – jpfollenius

1

El problema es con métodos anónimos en el dpr main.

Simplemente ponga su código en una rutina y llámelo en el dpr main y el informe de pérdida de memoria se ha ido.

procedure Main; 
var 
    Obj: ISomeInterface; 
begin 
    try 
    ReportMemoryLeaksOnShutdown := True; 
    Obj := TSomeInterface.Create; 
    Obj.AddListener (MyListener); 
    Obj.NotifyListeners; 
    Obj := nil; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end; 

begin 
    Main; 
end.