delphi - puede - metodos anonimos c#
Interfaces, métodos anónimos y fugas de memoria (3)
El problema es con los métodos anónimos en la página principal de dpr.
Simplemente ponga su código en una rutina y llámelo al dpr main y el informe de pérdida de memoria se habrá 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.
Este es un ejemplo construido. No quiero publicar el código original aquí. Aunque traté de extraer las partes relevantes.
Tengo una interfaz que gestiona una lista de oyentes.
TListenerProc = reference to procedure (SomeInt : ISomeInterface);
ISomeInterface = interface
procedure AddListener (Proc : TListenerProc);
end;
Ahora registro a un oyente:
SomeObj.AddListener (MyListener);
procedure MyListener (SomeInt : ISomeInterface);
begin
ExecuteSynchronized (procedure
begin
DoSomething (SomeInt);
end);
end;
Tengo fugas 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 la interfaz alife y la interfaz mantiene el método anónimo alife.
Dos preguntas:
- ¿Apoyas esa explicación? ¿O me estoy perdiendo algo más aquí?
- ¿Hay algo que pueda hacer al respecto?
¡Gracias por adelantado!
EDITAR : No es tan fácil reproducir esto en una aplicación lo suficientemente pequeña como para publicarlo aquí. Lo mejor que puedo hacer ahora es lo siguiente. El método anónimo no se libera 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.
Me parece un tema de referencia circular definido. Los métodos anónimos se administran a través de interfaces ocultas, y si TList<TListenerProc>
es propiedad del objeto en el que se implementa ISomeInterface, entonces tiene un problema de referencia circular.
Una posible solución sería poner un método ClearListeners en ISomeInterface que llame a .Clear
en el TList<TListenerProc>
. Mientras que nada más contenga una referencia a los métodos anónimos, eso hará que todos desaparezcan y eliminen sus referencias al ISomeInterface.
He publicado algunos artículos sobre la estructura y la implementación de métodos anónimos que pueden ayudarlo a comprender con qué está trabajando realmente y cómo funcionan un poco mejor. Puede encontrarlos en http://tech.turbu-rpg.com/category/delphi/anonymous-methods .
Su código está lejos de ser mínimo. El seguimiento:
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 puede ser trabajado o diseñado 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.