Интерфейсы, анонимные методы и утечки памяти
это построенный пример. Я не хочу публиковать исходный код здесь. Однако я попытался извлечь соответствующие части.
У меня есть интерфейс, который управляет списком слушателей.
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, а интерфейс сохраняет анонимный метод.
Два вопроса:
- Поддерживаете ли вы это объяснение? Или я пропустил что-то еще здесь?
- Есть ли что-нибудь, что я могу с этим сделать?
Спасибо заранее!
РЕДАКТИРОВАТЬ: это не так просто воспроизвести это в приложении, достаточно маленьком, чтобы опубликовать его здесь. Лучшее, что я могу сделать, это следующее. Анонимный метод не освобождается здесь:
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.
Ответы
Ответ 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 здесь). Это невозможно обработать или спроектировать. Похоже на ошибку в компиляторе.
Edit:
Или, может быть, это проблема обнаружения утечки памяти. Это не имеет никакого отношения к параметру, являющемуся интерфейсом, процедура без параметров приводит к той же "утечке". Очень странно.
Ответ 2
Мне кажется, что это определенная круговая справочная проблема. Анонимные методы управляются с помощью скрытых интерфейсов, и если TList<TListenerProc>
принадлежит объекту, на котором реализована функция ISomeInterface, тогда у вас есть проблема с круговой ссылкой.
Одним из возможных решений было бы поставить метод ClearListeners
на ISomeInterface, который вызывает .Clear
на TList<TListenerProc>
. Пока ничто иное не ссылается на анонимные методы, это заставило бы их исчезнуть и сбросить ссылки на ISomeInterface.
Я сделал несколько статей о структуре и реализации анонимных методов, которые могут помочь вам понять, с чем вы действительно работаете, и как они работают немного лучше. Вы можете найти их в http://tech.turbu-rpg.com/category/delphi/anonymous-methods.
Ответ 3
Проблема заключается в анонимных методах в главном dpr.
Просто поместите свой код в подпрограмму и вызовите это в главном 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.