Les objects identifiables sont-ils éliminés si le programme est arrêté inopinément?

Que se passe-t-il si le programme se ferme de manière inattendue (par exception ou si le processus est terminé)? Existe-t-il des situations telles que celle-ci (ou autre) où le programme se terminera, mais où les objects IDisposable ne seront pas correctement éliminés?

La raison pour laquelle je pose cette question est parce que j’écris du code qui communiquera avec un périphérique et je veux m’assurer qu’il n’y a aucune chance qu’il soit laissé en mauvais état.

Si la cause est une exception et est projetée depuis un bloc using ou un bloc try catch finally , elle sera éliminée comme il se doit. S’il n’est pas attrapé par un bloc using , il n’est pas automatiquement éliminé (comme ce n’est pas le cas lorsque l’application se ferme correctement).

Un échantillon:

 IDisposable d1 = new X(); using (IDisposable d2 = new X()) { throw new NotImplementedException(); } d1.Dispose(); 

d1 n’est pas éliminé, d2 est généralement. Certains types d’exceptions peuvent empêcher la gestion de l’ using blocs et également des plantages de programmes. Si la cause est une panne de courant ou un crash du système, vous ne pouvez rien faire non plus.

Si le programme se ferme de manière inattendue (par exemple, vous supprimez le processus), rien ne garantit que la méthode IDisposable.Dispose sera appelée. Vous feriez mieux de ne pas compter dessus pour de tels événements. La méthode Dispose doit être appelée manuellement par votre code. Ce n’est pas quelque chose que le CLR appellera automatiquement pour vous.

En plus des réponses de Pasortingck Hofman et Alexei, le nettoyage peut ne pas être effectué même si l’application se termine correctement.

Comme vous le savez probablement, la méthode Dispose n’est pas appelée lorsque le garbage collector collecte l’object qui implémente l’interface IDisposable . Mais le GC appellera la méthode Finalize , également appelée finaliseur. Vous devez y écrire votre logique de nettoyage à l’aide de Dispose Pattern . Et oui, .Net Framework essaiera d’exécuter tous les finaliseurs, mais rien ne garantit qu’ils seront exécutés.

À titre d’exemple, le programme ci-dessous dispose d’un finaliseur très long. Par conséquent, le .Net mettra fin au processus et vous ne verrez jamais le message.

 class FinalizableObject { ~FinalizableObject() { Thread.Sleep(50000); Console.WriteLine("Finalized"); } } class Program { static void Main(ssortingng[] args) { new FinalizableObject(); } } 

Cela peut être dû à une opération de longue durée, telle que la libération d’un descripteur de réseau, ou quelque chose d’autre qui nécessitera beaucoup de temps.

Par conséquent, vous ne devez jamais vous fier aux finaliseurs et aux objects jetables. Mais tous les descripteurs ouverts aux objects du kernel seront automatiquement fermés. Ne vous inquiétez donc pas.

Je vous recommanderai de lire quelques articles intéressants sur les finaliseurs et le GC, en plus des réponses:

  1. Tout le monde pense à la collecte des ordures de la mauvaise façon (Raymond Chen)
  2. Quand tout ce que tu sais est faux, première partie (Eric Lippert)
  3. Quand tout ce que tu sais est faux, deuxième partie (Eric Lippert)
  4. Terminer un processus (MSDN)

Un test très simple utilisant une application console montre que Dispose n’est pas appelé à la fin du processus:

 class DisposableTest : IDisposable { public void Dispose() { Console.WriteLine("Dispose called"); } } ... using (DisposableTest sw = new DisposableTest()) { Thread.Sleep(20000); } 

Tuer le processus avec Task Manager ne déclencherait pas la méthode Disposable.Dispose() . Attendre 20 secondes sera.

Ainsi, comme déjà mentionné, ne vous fiez pas aux objects jetables lorsque l’application se bloque ou est tuée. Cependant, des exceptions devraient le déclencher. Je me demande simplement si une exception telle que StackOverflowException ou OutOfMemoryException déclenchera toujours Dispose ().

[modifier]

Je viens de tester mes curiosités:

  • StackOverflowException obtient le processus terminé, donc pas de Dispose () n’est appelé
  • OutOfMemoryException autorise l’appel normal de Dispose ()

Oui, il y a de telles situations. Par exemple, appeler TerminateProcess , Environment.FailFast ou rencontrer une erreur CLR interne entraînera la fermeture du processus sans exécuter de code supplémentaire. Dans de telles situations, la meilleure chose à faire est de dire “bon”.

Même si le processus ne se ferme pas de manière inattendue, appeler Dispose est une action manuelle. Ce n’est pas quelque chose qui se fait au moment de l’exécution, sauf lorsqu’un object implémentant un finaliseur qui appelle Dispose est récupéré. Par conséquent, oublier de placer un object jetable dans une source d’ using ou de provoquer une fuite de mémoire qui maintient l’object en vie est un autre moyen d’appeler Dispose ne jamais être appelé.

Le seul nettoyage fiable est effectué par le système d’exploitation lorsqu’un processus est fermé: tous les descripteurs ouverts des objects système sont fermés. Lorsque le dernier descripteur est fermé, quel que soit le nettoyage implémenté dans le système d’exploitation ou le pilote. Si ce code de nettoyage ne fait pas partie d’un pilote mais est supposé être appelé par un processus utilisateur, vous ne pouvez que rendre votre code aussi robuste que possible ou mettre en œuvre un processus de surveillance qui gère le nettoyage pour vous.

IDisposable n’est qu’une interface. La manière dont ils sont traités n’a absolument rien de spécial. Lorsque vous appelez Dispose sur un IDisposable (explicitement ou via un bloc using), le contenu de votre méthode Dispose est appelé. Il ramasse les ordures comme n’importe quel autre object.

Le but de l’interface est de permettre à un implémenteur de définir le nettoyage d’un type pouvant avoir des ressources gérées ou non gérées, qui doivent être explicitement nettoyées.

Si ces ressources sont toutes gérées, la récupération de place peut suffire et les implémentations peuvent n’être que pour l’optimisation.

Si elles ne sont pas gérées ou ont une connexion avec des ressources non gérées, la récupération de place ne suffit probablement pas. C’est pourquoi l’implémentation complète recommandée d’IDisposable implique la gestion à la fois de l’élimination explicite et de l’élimination par le runtime (via un finaliseur).

Les arrêts de processus n’appellent pas Dispose et la finalisation n’est pas garantie … vous devez donc espérer que la destruction du processus est suffisante en soi.