17

to jest skonstruowany przykład. Nie chcę publikować oryginalnego kodu tutaj. Próbowałem jednak wyodrębnić odpowiednie części.Interfejsy, metody anonimowe i wycieki pamięci

Mam interfejs, który zarządza listą odbiorców.

TListenerProc = reference to procedure (SomeInt : ISomeInterface); 

ISomeInterface = interface 
    procedure AddListener (Proc : TListenerProc); 
end; 

Teraz zarejestrować słuchacza:

SomeObj.AddListener (MyListener); 

procedure MyListener (SomeInt : ISomeInterface); 
begin 
    ExecuteSynchronized (procedure 
         begin 
         DoSomething (SomeInt); 
         end); 
end; 

ja rozumiem wycieków pamięci. Zarówno metoda anonimowa, jak i interfejsy nigdy nie są zwalniane. Podejrzewam, że wynika to z jakiegoś okrężnego odniesienia tutaj. Anonimowa metoda utrzymuje życie interfejsu, a interfejs utrzymuje anonimową metodę.

dwa pytania:

  1. Czy popierasz to wyjaśnienie? Czy może tu brakuje czegoś innego?
  2. Czy mogę coś z tym zrobić?

Z góry dziękuję!


EDIT: To nie jest tak łatwe do odtworzenia tego w aplikacji na tyle mały, aby opublikować go tutaj. Najlepsze, co mogę teraz zrobić, to: Anonimowa metoda nie jest dostępna tutaj:

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

Pokażemy, jak działa AddListener. –

+0

Po prostu umieszczam je w 'TList .' – jpfollenius

+1

Cały kod, który widzę, wygląda dobrze. Problem musi znajdować się w ukrytej części. Czy możesz pokazać pełny przykład, który powoduje wyciek? –

Odpowiedz

8

Twój kod jest daleki od minimalnego. Następujące:

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. 

ma ten sam problem (Delphi 2009 tutaj). Tego nie można opracować ani zaprojektować. Wygląda mi na błąd w kompilatorze.

Edit:

A może jest to problem wykrywania wycieków pamięci. Nie ma to nic wspólnego z parametrem będącym interfejsem, procedura bez parametrów prowadzi do tego samego "wycieku". Bardzo dziwny.

+0

To wydaje się być problemem kompilatora. Jeśli przeniesiesz kod (blok try) z głównej procedury programu do procedury, a następnie wywołasz procedurę główną, nie zostanie zgłoszony żaden wyciek. –

+0

Dobra, sry, ponieważ przykładowy kod jest za długi. Sądziłem, że należy uwzględnić liczenie odwołań do interfejsu. Wydaje się, że tak nie jest. – jpfollenius

+0

i +1 za wydobycie istoty tego problemu. – jpfollenius

3

Wygląda mi na określony, kołowy temat odniesienia. Anonimowe metody są zarządzane za pomocą ukrytych interfejsów, a jeśli TList<TListenerProc> jest własnością obiektu, na którym jest zaimplementowana funkcja ISomeInterface, oznacza to, że mamy do czynienia z okrężnym problemem referencyjnym.

Jednym z możliwych rozwiązań byłoby umieszczenie metody ClearListeners na IsomeInterface, która wywołuje .Clear na. Dopóki nic innego nie będzie odwoływać się do anonimowych metod, to wszystkie znikają i zrzucają swoje odniesienia do ISomeInterface.

Zrobiłem kilka artykułów na temat struktury i wdrażania anonimowych metod, które mogą pomóc ci zrozumieć, z czym naprawdę pracujesz i jak działają trochę lepiej. Można je znaleźć pod numerem http://tech.turbu-rpg.com/category/delphi/anonymous-methods.

+0

A gdzie nazwać metodę 'ClearListeners'? – jpfollenius

+0

Przepraszam, jeśli jest to nieco ogólna odpowiedź, ale "podczas porządkowania". Kiedykolwiek chcesz, aby wszystko to wykraczało poza zakres. –

+0

Dzięki, Mason! Czy możesz rzucić okiem na moje zmienione pytanie i przykładowy kod? Kiedy włączyłem wywołanie metody "ClearListeners" na końcu metody głównej, metoda anonimowa nadal jest nieszczelna. – jpfollenius

1

Problem jest związany z metodami anonimowymi w głównym dpr.

Po prostu wprowadź swój kod do procedury i zadzwoń, że w raporcie głównym i raporcie przecieku pamięci nie ma.

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.