接口、匿名方法和内存泄漏
这是一个构建的例子。我不想在这里发布原始代码。我尝试提取相关部分。
我有一个管理侦听器列表的界面。
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.
this is a constructed example. I don't want to post the original code here. I tried to extract the relevant parts though.
I have an interface that manages a list of listeners.
TListenerProc = reference to procedure (SomeInt : ISomeInterface);
ISomeInterface = interface
procedure AddListener (Proc : TListenerProc);
end;
Now I register a listener:
SomeObj.AddListener (MyListener);
procedure MyListener (SomeInt : ISomeInterface);
begin
ExecuteSynchronized (procedure
begin
DoSomething (SomeInt);
end);
end;
I do get memory leaks. Both the anonymous method and the interfaces are never freed. I suspect that this is due to some kind of circular reference here. The anonymous method keeps the interface alife and the interface keeps the anonymous method alife.
Two questions:
- Do you support that explanation? Or am I missing something else here?
- Is there anything I can do about it?
Thanks in advance!
EDIT: It's not so easy to reproduce this in an application small enough to post it here. The best I can do by now is the following. The anonymous method does not get released here:
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您的代码远非最少。以下:
有完全相同的问题(此处为 Delphi 2009)。这是无法解决或设计的。在我看来就像编译器中的错误。
编辑:
或者这可能是内存泄漏检测的问题。它与参数是接口无关,无参数过程会导致相同的“泄漏”。很奇怪。
Your code is far from minimal. The following:
has the very same problem (Delphi 2009 here). This can't be worked or designed around. Looks to me like a bug in the compiler.
Edit:
Or maybe this is a problem of the memory leak detection. It has nothing to do with the parameter being an interface, a parameterless procedure leads to the same "leak". Very strange.
在我看来,这像是一个明确的循环引用问题。匿名方法是通过隐藏接口进行管理的,如果
TList
由实现 ISomeInterface 的对象拥有,那么就会遇到循环引用问题。一种可能的解决方案是在 ISomeInterface 上放置一个
ClearListeners
方法,该方法调用TList
上的.Clear
。只要没有其他东西持有对匿名方法的引用,就会使它们全部消失并删除对 ISomeInterface 的引用。我写了几篇关于匿名方法的结构和实现的文章,这些文章可能会帮助您了解您真正正在使用的内容以及它们如何更好地运行。您可以在 http://tech.turbu-rpg.com/category 找到它们/delphi/anonymous-methods。
Looks to me like a definite circular reference issue. Anonymous methods are managed through hidden interfaces, and if the
TList<TListenerProc>
is owned by the object that ISomeInterface is implemented on, then you've got a circular reference issue.One possible solution would be to put a
ClearListeners
method on ISomeInterface which calls.Clear
on theTList<TListenerProc>
. As long as nothing else is holding a reference to the anonymous methods, that would make them all vanish and drop their references to the ISomeInterface.I've done a few articles about the structure and implementation of anonymous methods that might help you understand what you're really working with and how they operate a little bit better. You can find them at http://tech.turbu-rpg.com/category/delphi/anonymous-methods.
问题出在 dpr main 中的匿名方法。
只需将代码放入例程中并在 dpr main 中调用该代码,内存泄漏报告就会消失。
The problem is with anonymous methods in the dpr main.
Just put your code in a routine and call that in the dpr main and the memory leak report is gone.