接口,匿名方法和内存泄漏

时间:2010-02-16 13:32:50

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

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

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

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. 我能做些什么吗?
  3. 提前致谢!


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

    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.
    

3 个答案:

答案 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.