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; 

Я получаю утечку памяти. И анонимный метод, и интерфейсы никогда не освобождаются. Я подозреваю, что это связано с какой-то циркулярной ссылкой здесь. Анонимный метод поддерживает интерфейс alife, а интерфейс сохраняет анонимный метод.

Два вопроса:

  1. Поддерживаете ли вы это объяснение? Или я пропустил что-то еще здесь?
  2. Есть ли что-нибудь, что я могу сделать?

Заранее благодарен!


EDIT: Это не так легко воспроизвести это в приложении достаточно малы, чтобы разместить его здесь. Лучшее, что я могу сделать, это следующее. Анонимный метод не освобождается здесь:

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

Вы должны показать нам, как работает AddListener. –

+0

Я просто положил их в «TList .» – jpfollenius

+1

Весь код, который я вижу, выглядит хорошо. Проблема должна быть в скрытой части. Можете ли вы показать полный пример, который вызывает утечку? –

ответ

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

Edit:

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

+0

Это, похоже, проблема с компилятором. Если вы переместите код (блок try) из основной процедуры программы в процедуру и затем выполните главный вызов процедуры, утечки не сообщается. –

+0

Хорошо, прошу, чтобы код примера был слишком длинным. Я думал, что учет ссылок на интерфейс должен быть задействован. Кажется, это не так. – jpfollenius

+0

и +1 для извлечения сущности этой проблемы. – jpfollenius

3

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

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

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

+0

И где вызвать метод ClearListeners? – jpfollenius

+0

Извините, если это несколько общий ответ, но «во время очистки». Всякий раз, когда вы хотите, чтобы все это выходило за рамки. –

+0

Спасибо, Мейсон! Не могли бы вы взглянуть на мой отредактированный вопрос и пример кода? Когда я положил вызов 'ClearListeners' в конце основного метода, анонимный метод все еще просочился. – jpfollenius

1

Проблема с анонимными методами в главном 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. 
Смежные вопросы