Delphi 接口、匿名方法和内存泄漏
这是一个构造的示例。我不想在这里发布原始代码。不过,我试图提取相关部分 我有一个界面,可以管理侦听器列表Delphi 接口、匿名方法和内存泄漏,delphi,memory-management,interface,delphi-2010,anonymous-methods,Delphi,Memory Management,Interface,Delphi 2010,Anonymous Methods,这是一个构造的示例。我不想在这里发布原始代码。不过,我试图提取相关部分 我有一个界面,可以管理侦听器列表 TListenerProc = reference to procedure (SomeInt : ISomeInterface); ISomeInterface = interface procedure AddListener (Proc : TListenerProc); end; 现在我注册一个侦听器: SomeObj.AddListener (MyListener)
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;
我确实有内存泄漏。匿名方法和接口都不会被释放。我怀疑这是由于某种循环引用。匿名方法保持接口alife,接口保持匿名方法alife
两个问题:
编辑:在一个小到可以发布到这里的应用程序中复制它并不容易。到目前为止,我能做的最好的事情如下。此处未发布匿名方法:
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.
程序TestMemLeak;
{$APPTYPE控制台}
使用
泛型。集合,系统;
类型
接口=接口;
TListenerProc=程序参考(SomeInt:ISomeInterface);
接口
[{DB5A336B-3F79-4059-8933-27699203D1B6}]
程序AddListener(Proc:TListenerProc);
过程通知侦听器;
程序测试;
结束;
TSomeInterface=class(TInterfacedObject,ISomeInterface)
严格保密
FListeners:TList;
受保护的
程序AddListener(Proc:TListenerProc);
过程通知侦听器;
程序测试;
公众的
构造函数创建;
毁灭者毁灭;推翻
结束;
过程TSomeInterface.AddListener(Proc:TListenerProc);
开始
FListeners.Add(Proc);
结束;
构造函数TSomeInterface.Create;
开始
FListeners:=TList.Create;
结束;
析构函数TSomeInterface.Destroy;
开始
FreeAndNil(FListeners);
继承;
结束;
程序TSomeInterface.NotifyListeners;
变量
监听器:TListenerProc;
开始
对于Flistener中的侦听器
听者(自我);
结束;
程序TSomeInterface.测试;
开始
//无所事事
结束;
程序执行(Proc:TProc);
开始
过程;
结束;
程序MyListener(SomeInt:ISomeInterface);
开始
执行(程序)
开始
somint.试验;
(完),;
结束;
变量
Obj:接口;
开始
尝试
ReportMemoryLeaksOnShutdown:=True;
Obj:=TSomeInterface.Create;
Obj.AddListener(MyListener);
Obj.NotifyListeners;
Obj:=零;
除了
关于E:Exception-do
Writeln(E.ClassName,“:”,E.Message);
结束;
结束。
在我看来,这是一个明确的循环参考问题。匿名方法是通过隐藏接口来管理的,如果TList
属于在其上实现ISomeInterface的对象,那么就会出现循环引用问题
一个可能的解决方案是在接口上放置一个ClearListeners
方法,该方法调用列表上的.Clear
。只要没有其他东西保存对匿名方法的引用,这将使它们全部消失并删除对接口的引用
我已经写了几篇关于匿名方法的结构和实现的文章,这些文章可能会帮助您了解您真正使用的是什么,以及它们如何更好地运行。您可以在中找到它们。您的代码远远不是最小的。以下是:
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)。这是不能工作或设计周围。在我看来就像编译器中的一个bug
编辑:
或者这可能是内存泄漏检测的问题。它与作为接口的参数无关,无参数过程会导致相同的“泄漏”。非常奇怪。问题在于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.
你应该告诉我们AddListener是如何工作的。我只是把它们放在一个t列表中。
我看到的所有代码看起来都不错。问题一定在隐藏的部分。你能给出一个导致泄漏的完整例子吗?见我对Mghie答案的评论。检查一下这是否有帮助。在哪里调用ClearListeners
方法?如果这是一个有点通用的答案,很抱歉,但是“在清理过程中”。只要你想让这一切超出范围。谢谢你,梅森!你能看一下我编辑的问题和示例代码吗?当我在main方法的末尾调用ClearListeners
时,匿名方法仍然泄漏。这似乎是编译器的问题。如果您将代码(try块)从程序的主例程移动到一个过程,然后让主程序调用该过程,则不会报告泄漏。好的,请注意示例代码太长。我认为必须涉及接口引用计数。似乎不是这样。和+1用于提取这个问题的本质。@Mason:我也发现了这一点。不幸的是,在原始代码中这并不是那么容易。因此,除了在这里避免匿名方法之外,似乎没有其他解决办法。@Smasher:对于过程引用,接口引用计数肯定是涉及的。当您在调试器中单步执行此操作时,您将看到在分配给侦听器的过程中,ref计数从1变为2变为1。它不会回到零,或者只有在FastMM4已经抱怨过之后才会回到零。这也解释了为什么将其移动到另一个过程中会起作用,这引入了另一个作用域级别,在程序退出之前实际上可以保留该级别。