Интерфейсы, анонимные методы и утечки памяти - PullRequest
17 голосов
/ 16 февраля 2010

это построенный пример. Я не хочу размещать оригинальный код здесь. Я пытался извлечь соответствующие части, хотя.

У меня есть интерфейс, который управляет списком слушателей.

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.

Ответы [ 3 ]

8 голосов
/ 16 февраля 2010

Ваш код далеко не минимален. Следующее:

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 здесь). Это не может быть обработано или разработано. Выглядит как ошибка в компиляторе.

Edit:

А может, это проблема обнаружения утечки памяти. Это не имеет никакого отношения к параметру, являющемуся интерфейсом, процедура без параметров приводит к той же самой «утечке». Очень странно.

3 голосов
/ 16 февраля 2010

Похоже на определенный круговой вопрос. Управление анонимными методами осуществляется через скрытые интерфейсы, и если TList<TListenerProc> принадлежит объекту, на котором реализован ISomeInterface, то возникает проблема с циклической ссылкой.

Одним из возможных решений было бы поместить метод ClearListeners в ISomeInterface, который вызывает .Clear в TList<TListenerProc>. Пока ничто иное не содержит ссылку на анонимные методы, это заставит их всех исчезнуть и отбросит их ссылки на ISomeInterface.

Я написал несколько статей о структуре и реализации анонимных методов, которые могут помочь вам понять, с чем вы действительно работаете и как они работают немного лучше. Вы можете найти их в http://tech.turbu -rpg.com / category / delphi / anonymous-method .

1 голос
/ 26 марта 2014

Проблема с анонимными методами в dpr main.

Просто поместите ваш код в подпрограмму и вызовите его в главной dpr, и отчет об утечке памяти исчезнет.

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