这是一个构造的例子。我不想在这里发布原始代码。我尝试提取相关部分。
我有一个管理侦听器列表的界面。
TListenerProc = reference to procedure (SomeInt : ISomeInterface);
ISomeInterface = interface
procedure AddListener (Proc : TListenerProc);
end;
现在我注册了一个监听器:
SomeObj.AddListener (MyListener);
procedure MyListener (SomeInt : ISomeInterface);
begin
ExecuteSynchronized (procedure
begin
DoSomething (SomeInt);
end);
end;
我确实得到了内存泄漏。永远不会释放匿名方法和接口。我怀疑这是由于某种循环引用。匿名方法保持界面生效,界面保持匿名方法生效。
两个问题:
提前致谢!
编辑:在一个小到足以在此处发布的应用程序中重现这一点并不容易。我现在能做的最好的事情如下。匿名方法不会在这里发布:
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 :(得分:8)
您的代码远非微不足道。以下内容:
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.
有同样的问题(Delphi 2009)。这不能工作或设计。在我看来就像编译器中的一个错误。
修改强>
或者这可能是内存泄漏检测的问题。它与作为接口的参数无关,无参数的过程导致相同的“泄漏”。很奇怪。
答案 1 :(得分:3)
在我看来,这是一个明确的循环参考问题。匿名方法通过隐藏接口进行管理,如果TList<TListenerProc>
由实现ISomeInterface的对象所拥有,那么您就会遇到循环引用问题。
一种可能的解决方案是在ISomeInterface上放置一个ClearListeners
方法,该方法在.Clear
上调用TList<TListenerProc>
。只要没有其他任何东西持有对匿名方法的引用,那将使它们全部消失并删除它们对ISomeInterface的引用。
我已经做了一些关于匿名方法的结构和实现的文章,可能有助于您了解您真正使用的内容以及它们如何更好地运行。您可以在http://tech.turbu-rpg.com/category/delphi/anonymous-methods找到它们。
答案 2 :(得分:1)
问题在于dpr main中的匿名方法。
只需将代码放入例程并在dpr main中调用它,内存泄漏报告就会消失。
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.