17

Ceci est un exemple construit. Je ne veux pas poster le code original ici. J'ai essayé d'extraire les parties pertinentes cependant.Interfaces, méthodes anonymes et fuites de mémoire

J'ai une interface qui gère une liste d'écouteurs.

TListenerProc = reference to procedure (SomeInt : ISomeInterface); 

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

Maintenant, j'enregistrer un écouteur:

SomeObj.AddListener (MyListener); 

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

Je reçois des fuites de mémoire. La méthode anonyme et les interfaces ne sont jamais libérées. Je soupçonne que cela est dû à une sorte de référence circulaire ici. La méthode anonyme maintient l'interface alife et l'interface maintient la méthode anonyme alife.

Deux questions:

  1. Approuvez-vous cette explication? Ou est-ce que je manque quelque chose d'autre ici?
  2. Y a-t-il quelque chose que je puisse faire à ce sujet?

Merci d'avance!


EDIT: Il est pas si facile de reproduire cela dans une application assez petit pour poster ici. Le mieux que je puisse faire maintenant est le suivant. La méthode anonyme n'est pas publiée ici:

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

Vous devriez nous montrer comment fonctionne AddListener. –

+0

Je les ai juste mis dans un 'TList .' – jpfollenius

+1

Tout le code que je vois a l'air bien. Le problème doit être dans la partie cachée. Pouvez-vous montrer un exemple complet qui produit la fuite? –

Répondre

8

Votre code est loin d'être minime. Ce qui suit:

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. 

a le même problème (Delphi 2009 ici). Cela ne peut pas être travaillé ou conçu autour. On dirait que c'est un bug dans le compilateur.

Edit:

Ou peut-être cela est un problème de la détection des fuites de mémoire. Cela n'a rien à voir avec le fait que le paramètre soit une interface, une procédure sans paramètre conduit à la même "fuite". Très étrange.

+0

Cela semble être un problème de compilateur. Si vous déplacez le code (le bloc try) de la routine principale du programme vers une procédure, puis appelez la procédure, aucune fuite n'est signalée. –

+0

Ok, sry pour l'exemple de code étant trop long. Je pensais que le comptage des références d'interface devait être impliqué. Il semble que ce n'est pas le cas. – jpfollenius

+0

et +1 pour extraire l'essence de ce problème. – jpfollenius

3

Cela me semble être un problème de référence circulaire défini. Les méthodes anonymes sont gérées via des interfaces cachées, et si le TList<TListenerProc> appartient à l'objet sur lequel ISomeInterface est implémenté, alors vous avez un problème de référence circulaire.

Une solution possible serait de mettre une méthode ClearListeners sur ISomeInterface qui appelle .Clear sur TList<TListenerProc>. Tant que rien d'autre ne contient une référence aux méthodes anonymes, cela les ferait disparaître et laisser tomber leurs références à l'ISomeInterface.

J'ai fait quelques articles sur la structure et l'implémentation de méthodes anonymes qui pourraient vous aider à comprendre ce que vous travaillez vraiment et comment ils fonctionnent un peu mieux. Vous pouvez les trouver au http://tech.turbu-rpg.com/category/delphi/anonymous-methods.

+0

Et où appeler la méthode 'ClearListeners'? – jpfollenius

+0

Désolé si c'est une réponse quelque peu générique, mais "pendant le nettoyage". Chaque fois que vous voulez que tout cela soit hors de portée. –

+0

Merci pour l'instant, Mason! Pourriez-vous jeter un oeil à la question que j'ai éditée et à l'exemple de code? Lorsque je mets l'appel à ClearListeners à la fin de la méthode principale, la méthode anonyme est toujours fuite. – jpfollenius

1

Le problème est avec les méthodes anonymes dans le dpr principal.

Il suffit de mettre votre code dans une routine et appelez cela dans le dpr principal et le rapport de fuite de mémoire est parti.

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.