2009-11-11 8 views
0

Je n'arrive pas à comprendre le comportement de GC.Collect() sous la présence d'une classe Substituant Object.Finalize(). Ceci est mon code de base:Object.Finalize() override et GC.Collect()

namespace test 
{ 
class Foo 
{ 
    ~Foo() { Console.WriteLine("Inside Foo.Finalize()"); } 
} 

static class Program 
{ 

    static void Main() 
    { 
    { 
    Foo bar = new Foo(); 
    } 

    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 

    Console.ReadLine(); 
    } 
} 

} 

Contrairement à ce que je m'y attendais, je ne reçois que la sortie de la console à la fin du programme et non après GC.WaitForPendingFinalizers()

Répondre

12

Ni le compilateur ni le moteur d'exécution ne sont requis pour garantir que les locals hors champ ont effectivement la durée de vie de leur contenu tronquée. Il est parfaitement légal pour le compilateur ou le runtime de traiter cela comme si les accolades n'étaient pas là, dans le but de calculer la durée de vie. Si vous avez besoin d'un nettoyage par accolade, implémentez IDisposable et utilisez le bloc "using".

MISE À JOUR:

En ce qui concerne votre question "pourquoi est-ce différent optimisé vs builds unoptimized", eh bien, regardez la différence de codegen.

non optimisé:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  28 (0x1c) 
    .maxstack 1 
    .locals init (class test.Foo V_0) 
    IL_0000: nop 
    IL_0001: nop 
    IL_0002: newobj  instance void test.Foo::.ctor() 
    IL_0007: stloc.0 
    IL_0008: nop 
    IL_0009: call  void [mscorlib]System.GC::Collect() 
    IL_000e: nop 
    IL_000f: call  void [mscorlib]System.GC::WaitForPendingFinalizers() 
    IL_0014: nop 
    IL_0015: call  string [mscorlib]System.Console::ReadLine() 
    IL_001a: pop 
    IL_001b: ret 
} // end of method Program::Main 

OPTIMISE:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  23 (0x17) 
    .maxstack 8 
    IL_0000: newobj  instance void test.Foo::.ctor() 
    IL_0005: pop 
    IL_0006: call  void [mscorlib]System.GC::Collect() 
    IL_000b: call  void [mscorlib]System.GC::WaitForPendingFinalizers() 
    IL_0010: call  string [mscorlib]System.Console::ReadLine() 
    IL_0015: pop 
    IL_0016: ret 
} // end of method Program::Main 

De toute évidence, une énorme différence. Clairement dans la construction non optimisée, la référence est stockée dans l'emplacement local zéro et n'est jamais supprimée jusqu'à la fin de la méthode. Par conséquent, le CPG ne peut pas récupérer la mémoire avant la fin de la méthode. Dans la construction optimisée, la référence est stockée sur la pile, immédiatement retirée de la pile, et le GC est libre de la récupérer puisqu'il n'y a plus de référence valide sur la pile.

+0

Ok. ça explique beaucoup. Merci. –

+0

Excellente mise à jour de votre message, Eric. Merci beaucoup! –

3

Le garbage collector ne fait absolument aucune garantie quant au moment où les données seront collecté. C'est l'une des raisons pour lesquelles vous devez utiliser l'instruction using pour disposer des objets jetables.

GC.WaitForPendingFinalizers() attend uniquement les finaliseurs qui ont été collectés - si un objet n'a pas encore été collecté, il ne fait rien.

Il est fort probable que le compilateur garde un pointeur sur lui même si vous n'avez plus accès au nom.

Je voudrais essayer de mettre l'appel à nouveau Foo() dans une fonction séparée - qui pourrait aider, bien qu'une fois de plus - aucune garantie.

2

bar est toujours portée lorsque vous appelez GC.Collect() et GC.WaitForPendingFinalizers()

Foo ne met pas en œuvre aussi IDisposable().

Je suppose que le GC n'est pas encore prêt à libérer la mémoire utilisée par votre objet Foo, et vous ne pouvez pas appeler explicitement Dispose(). Par conséquent, il est disposé lorsque l'application termine son exécution.

+0

Après avoir implémenté disposer, Foo est en effet correctement ramassé les ordures quand je m'y attends aussi. Merci. –

0

Je ne pense pas que la portée fonctionne de la même manière qu'en C++. Je pense que les variables sont effectivement valables jusqu'à la sortie de la fonction, par exemple:

class Program 
{ 
    class Foo 
    { 
     ~Foo() { Console.WriteLine("Test"); } 
    } 


    static void Test() 
    { 
     Foo foo = new Foo(); 
    } 

    static void Main() 
    { 
     Test(); 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 

     Console.ReadLine(); 
    } 
} 

Si vous pensez à l'IL, alors il n'y a pas une telle chose comme une accolade dans les variables IL et locales ont toujours au moins la portée de la fonction.

+0

Vous ne savez pas exactement ce que votre code essaie de prouver. Dans tous les cas, vous pouvez appliquer la portée sous C# en utilisant des accolades. Et étant des variables locales, celles-ci peuvent être ramassées par des ordures tout en restant sous la même fonction. Pourquoi cela ne se passait pas est expliqué sur les autres postes. –

0

Voici un autre grand article sur GC peut se produire au moment de l'exécution de code inattendu:

vie, GC.KeepAlive, gérer le recyclage - par cbrumme http://blogs.msdn.com/b/cbrumme/archive/2003/04/19/51365.aspx?wa=wsignin1.0

Ma question est de savoir comment puis-je reproduire forcé GC au point mentionné dans l'article? J'ai essayé de mettre GC.Collect() au début de OperateOnHandle(), et ai défini le destructeur pour la classe C mais ne semble pas fonctionner. Destructeur est invoqué toujours à la fin du programme.