Ok, il est connu que GC appelle implicitement Finalize
sur des objets lorsqu'il identifie cet objet comme étant un déchet. Mais que se passe-t-il si je fais un GC.Collect()
? Les finaliseurs sont-ils toujours exécutés ? Une question stupide peut-être, mais quelqu'un m'a posé cette question et j'ai répondu un "Oui" et ensuite j'ai pensé : " Est-ce que c'était tout à fait correct ? "
Réponses
Trop de publicités?Ok, il est connu que la GC appelle implicitement les méthodes Finalize sur les objets quand elle identifie cet objet comme un déchet.
Non non non. Ce n'est pas connu sous le nom de parce qu'afin d'être connaissance une déclaration doit être vrai . Cette déclaration est faux . Le ramasseur d'ordures n'exécute pas les finaliseurs comme il le fait pour les traces. qu'il s'exécute lui-même ou que vous appeliez Collect
. Le thread de finalisation exécute les finaliseurs après que le collecteur de traçage ait trouvé les déchets. et cela arrive de manière asynchrone par rapport à un appel à Collect
. (Si cela se produit, ce qui peut ne pas être le cas, comme l'indique une autre réponse). En d'autres termes, vous ne pouvez pas compter sur le fait que le thread du finisseur s'exécute avant que le contrôle ne revienne de Collect
.
Voici un aperçu très simplifié de son fonctionnement :
- Lorsqu'une collecte a lieu, le fil de traçage du ramasseur d'ordures trace les racines - les objets connus pour être vivants, et tous les objets auxquels ils font référence, et ainsi de suite - pour déterminer les objets morts.
- Les objets "morts" qui ont des finaliseurs en attente sont déplacés dans la file d'attente des finaliseurs. La file d'attente du finisseur est une racine . Donc ces objets "morts" sont en fait toujours en vie .
- Le thread de finalisation, qui est généralement un thread différent du thread de traçage GC, finit par s'exécuter et vide la file d'attente de finalisation. Ces objets deviennent alors vraiment morts, et sont collectés dans la file d'attente de la GC. suivant sur le fil de traçage. (Bien sûr, comme ils viennent de survivre à la première collection, ils pourraient être dans une génération supérieure).
Comme je l'ai dit, c'est trop simplifié ; les détails exacts du fonctionnement de la file d'attente des finaliseurs sont un peu plus compliqués que cela. Mais cela suffit à faire passer l'idée. Le résultat pratique ici est que vous ne pouvez pas supposer qu'appeler Collect
exécute également des finaliseurs parce que ce n'est pas le cas. Laissez-moi le répéter une fois de plus : la partie traçage du ramasseur de déchets fait no exécuter les finaliseurs y Collect
n'exécute que la partie traçage du mécanisme de collecte.
Appelez le bien nommé WaitForPendingFinalizers
après avoir appelé Collect
si vous voulez garantir que tous les finaliseurs ont été exécutés. Cela mettra en pause le thread actuel jusqu'à ce que le thread du finisseur vide la file d'attente. Et si vous voulez vous assurer que les objets finalisés ont récupéré leur mémoire, vous devrez appeler Collect
a segundo temps.
Et bien sûr, il va sans dire que vous ne devriez faire cela qu'à des fins de débogage et de test. Ne faites jamais ces bêtises dans du code de production sans vraiment le faire, vraiment bonne raison.
En fait, la réponse est "Cela dépend". En fait, il y a un thread dédié qui exécute tous les finaliseurs. Cela signifie que l'appel à GC.Collect
ne déclenche que ce processus et l'exécution de tous les finaliseurs sera appelée de manière asynchrone.
Si vous voulez attendre que tous les finaliseurs soient appelés, vous pouvez utiliser l'astuce suivante :
GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
Oui, mais pas tout de suite. Cet extrait est tiré de Garbage Collection : Gestion automatique de la mémoire dans le Microsoft .NET Framework (Magazine MSDN) (*)
"Lorsqu'une application crée un nouvel objet, l'opérateur new alloue la mémoire à partir du tas. Si le type de l'objet contient une méthode Finalize un pointeur sur l'objet est placé dans la file d'attente de finalisation. de finalisation. La file d'attente de finalisation est une structure de données interne contrôlée par le ramasseur d'ordures. Chaque entrée de la file d'attente pointe vers un objet qui doit être appelé par sa méthode Finalize avant que la mémoire de l'objet puisse être récupérée.
Quand un GC se produit ... le ramasseur de déchets parcourt la file d'attente de finalisation à la recherche de pointeurs vers ces objets. Quand un pointeur est trouvé, le pointeur est retiré de la file de finalisation et ajouté à la file queue freachable (prononcez "F-reachable"). La file freachable est une autre structure de données interne contrôlée par le ramasseur de déchets. Chaque pointeur dans la queue freachable identifie un objet qui est prêt à être appelé par sa méthode Finalize.
Il existe un fil d'exécution spécial dédié à l'appel de Finalize pour appeler les méthodes Finalize. Lorsque la file d'attente est vide (ce qui est généralement le cas), t cas), ce thread dort. Mais lorsque des entrées apparaissent, ce thread se réveille, retire chaque entrée de la file et appelle la méthode Finalize de chaque objet. de chaque objet. Pour cette raison, vous ne devez pas exécuter de code dans une méthode Finalize qui fait des suppositions sur le thread qui exécute le code. code. Par exemple, évitez d'accéder au stockage local du thread dans la méthode Finalize".
(*) Depuis novembre 2000, les choses peuvent avoir changé depuis.
Lorsque les ordures sont collectées (que ce soit en réponse à une pression de la mémoire ou GC.Collect()
), les objets nécessitant une finalisation sont placés dans la file d'attente de finalisation.
A moins que vous n'appeliez GC.WaitForPendingFinalizers()
En effet, les finaliseurs peuvent continuer à s'exécuter en arrière-plan longtemps après la fin du ramassage des ordures.
De plus, il n'y a aucune garantie que les finaliseurs seront appelés. du tout . De MSDN ...
Il se peut que la méthode Finalize ne soit pas exécutée jusqu'au bout ou qu'elle ne soit pas exécutée à l'heure prévue. dans les circonstances exceptionnelles suivantes :
- Un autre finalisateur se bloque indéfiniment (il entre dans une boucle infinie, tente d'obtenir un verrou qu'il ne pourra jamais obtenir, etc.) Parce que le d'exécution tente d'exécuter les finaliseurs jusqu'au bout, les autres finaliseurs peuvent peuvent ne pas être appelés si un finalisateur se bloque indéfiniment.
- Le processus se termine sans laisser au runtime la possibilité de faire le ménage. Dans ce cas, la première notification de la fin du processus par le runtime processus est une notification DLL_PROCESS_DETACH.
Le runtime continue à finaliser les objets pendant l'arrêt uniquement pendant les périodes suivantes le nombre d'objets finalisables continue de diminuer.