【问题标题】:Interfaces, Anonymous Methods and Memory Leaks接口、匿名方法和内存泄漏
【发布时间】:2011-01-17 09:48:17
【问题描述】:

这是一个构造示例。我不想在这里发布原始代码。不过我尝试提取相关部分。

我有一个管理侦听器列表的界面。

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;

我确实遇到了内存泄漏。匿名方法和接口都永远不会被释放。我怀疑这是由于这里的某种循环引用。匿名方法使接口保持活力,接口使匿名方法保持活力。

两个问题:

  1. 你支持这个解释吗?还是我在这里错过了其他东西?
  2. 我能做些什么吗?

提前致谢!


编辑:在一个小到可以在此处发布的应用程序中重现此内容并不容易。我现在能做的最好的事情如下。匿名方法在这里没有发布:

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.

【问题讨论】:

  • 您应该向我们展示 AddListener 的工作原理。
  • 我只是把它们放在TList &lt;TListenerProc&gt;.
  • 我看到的所有代码看起来都不错。问题一定出在隐藏的部分。你能展示一个产生泄漏的完整例子吗?
  • 查看我对 Mghie 回答的评论。检查看看是否有帮助。

标签: delphi memory-management interface delphi-2010 anonymous-methods


【解决方案1】:

您的代码远非最小。以下:

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)。这是无法解决或设计的。在我看来,这就像编译器中的错误。

编辑:

或者这可能是内存泄漏检测的问题。它与作为接口的参数无关,无参数过程会导致相同的“泄漏”。很奇怪。

【讨论】:

  • 这似乎是一个编译器问题。如果将代码(try 块)从程序的主例程移动到一个过程,然后让主调用该过程,则不会报告泄漏。
  • 好的,抱歉示例代码太长了。我认为必须涉及接口引用计数。好像不是这样的。
  • 和 +1 用于提取这个问题的本质。
  • @Mason:我也发现了这一点。不幸的是,在原始代码中并不是那么容易。所以除了在这个地方避免匿名方法之外似乎没有其他解决方法。
  • @Smasher: 涉及接口引用计数肯定,用于过程引用。当您在调试器中单步执行此操作时,您将看到在分配给Listener 期间引用计数从 1 变为 2 再到 1。它只是不会回到零,或者只有在 FastMM4 已经抱怨之后才会这样做。这也可以解释为什么将其移至另一个过程有效,这引入了另一个范围级别,实际上可以在程序退出之前留下。
【解决方案2】:

在我看来,这是一个明确的循环引用问题。匿名方法是通过隐藏接口管理的,如果 TList&lt;TListenerProc&gt; 归实现 ISomeInterface 的对象所有,那么您就会遇到循环引用问题。

一种可能的解决方案是在 ISomeInterface 上放置一个 ClearListeners 方法,该方法在 TList&lt;TListenerProc&gt; 上调用 .Clear。只要没有其他东西持有对匿名方法的引用,这将使它们全部消失并放弃对 ISomeInterface 的引用。

我写了几篇关于匿名方法的结构和实现的文章,这些文章可能会帮助您了解您真正使用的是什么以及它们如何更好地运行。你可以在http://tech.turbu-rpg.com/category/delphi/anonymous-methods找到他们。

【讨论】:

  • 在哪里调用ClearListeners方法?
  • 对不起,如果这是一个有点笼统的答案,但“在清理期间”。每当您希望所有这些都超出范围时。
  • 谢谢,梅森!你能看看我编辑的问题和示例代码吗?当我在main方法的最后调用ClearListeners时,匿名方法还是泄露了。
【解决方案3】:

问题在于 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.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 2015-12-28
    • 1970-01-01
    • 2023-04-09
    • 2017-01-31
    相关资源
    最近更新 更多