Le point de Céder est de libérer des ressources non managées. Il doit être fait à un certain point, sinon ils ne seront jamais nettoyé. Le garbage collector ne sais pas comment appeler DeleteHandle()
sur une variable de type IntPtr
, il ne sait pas si elle doit ou non appel DeleteHandle()
.
Remarque: Ce qui est une ressource non managée? Si
vous l'avez trouvé dans le Microsoft .NET
Cadre: c'est réussi. Si vous êtes allé
farfouillé MSDN vous-même, c'est
non managé. Tout ce que vous avez utilisé
P/Invoke appels à obtenir à l'extérieur de la
confortable monde de tout
disponible pour vous dans le .NET Framwork
ne sont pas gérés, et vous êtes maintenant
responsable du nettoyage.
L'objet que vous avez créé doit exposer certains de la méthode, que le monde extérieur peut faire appel, afin de nettoyer les ressources non managées. Il y a même un nom normalisé pour cette méthode:
public void Dispose()
Il y a même une interface créée, IDisposable
, qui vient d'une méthode:
public interface IDisposable
{
void Dispose()
}
Afin de vous rendre votre objet d'exposer l' IDisposable
interface, et de cette façon vous promets que vous avez écrit que la seule méthode pour nettoyer vos ressources non managées:
public void Dispose()
{
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
}
Et vous avez terminé. Sauf que vous pouvez faire mieux.
Que faire si votre objet a alloué une 250MB Système.De dessin.Bitmap (c'est à dire l' .NET géré classe Bitmap) comme une sorte de mémoire tampon de trame? Bien sûr, cela est géré .NET de l'objet, et le garbage collector gratuit. Mais voulez-vous vraiment quitter 250 MO de mémoire juste assis là en attente pour le garbage collector pour finalement venir le long et de les libérer? Que faire si il y a une base de données ouverte de connexion? Certes, nous ne voulons pas que la connexion séance ouverte, en attente pour le GC pour finaliser l'objet.
Si l'utilisateur a appelé Dispose()
(ce qui signifie qu'ils ne sont plus du plan d'utilisation de l'objet), pourquoi ne pas se débarrasser de ceux gaspillage des bitmaps et des connexions de base de données?
Alors maintenant, nous allons:
- se débarrasser des ressources non managées (parce que nous avons), et
- se débarrasser de la gestion des ressources (parce que nous voulons être utile)
Donc, nous allons mettre à jour notre Dispose()
méthode pour se débarrasser de ces objets gérés:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
Et tous est bon, sauf que vous pouvez faire mieux!
Que faire si la personne a oublié d'appeler Dispose()
sur votre objet? Alors qu'ils seraient à la fuite de certains non géré ressources!
Remarque: Ils ne fuit pas géré les ressources, parce que finalement le
garbage collector se déroulera, sur
un thread d'arrière-plan, et de libérer la
associée de la mémoire inutilisée
objets. Il s'agit de votre objet, et tout des objets gérés que vous utilisez (par exemple, l'image et le DbConnection).
Si la personne a oublié d'appeler Dispose()
, nous pouvons encore sauver leur bacon! Nous avons encore un moyen de l'appeler pour eux: lorsque le garbage collector, enfin, se déplace à libérer (c'est à dire la finalisation) de notre objet.
Remarque: Le garbage collector sera finalement libre de tous les objets gérés.
Lorsqu'il le fait, il appelle l' Finalize
méthode sur l'objet. Le GC ne savent pas, ou
de soins, sur votre Disposerde la méthode.
C'était juste un nom que nous avons choisi pour
une méthode que nous appelons lorsque l'on veut obtenir
débarrasser de non géré choses.
La destruction de notre objet par le Garbage collector est le parfait moment pour libérer ces satanés des ressources non managées. Nous faisons cela en remplaçant l' Finalize()
méthode.
Remarque: En C#, vous n'avez pas explicitement remplacer l' Finalize()
méthode. Vous écrivez une méthode qui ressemble à un destructeur C++ , et le compilateur prend que pour être votre mise en œuvre de l' Finalize()
méthode:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Mais il y a un bug dans le code. Vous voyez, le garbage collector s'exécute sur un thread d'arrière-plan, vous ne savez pas l'ordre dans lequel les deux objets sont détruits. Il est tout à fait possible que, dans votre Dispose()
code, l' géré objet que vous essayez de vous débarrasser de (parce que tu voulais être utile) n'est plus là:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); <-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); <-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Donc, ce que vous avez besoin est une façon pour Finalize()
de raconter Dispose()
qu'il devrait pas toucher tout géré les ressources (parce qu'ils pourraient ne pas être là plus), tout en libérant des ressources non managées.
La configuration standard pour ce faire est d'avoir Finalize()
et Dispose()
à la fois appel à un tiers(!) méthode; où l'on passe un Booléen disant: si vous appelez à partir d' Dispose()
(par opposition à l' Finalize()
), ce qui signifie qu'il est sûr de gratuit ressources gérées.
Cette interne méthode pourrait être donné quelques arbitraire nom comme "CoreDispose", ou "MyInternalDispose", mais c'est la tradition à l'appellent Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Mais un des plus utiles nom du paramètre peut être:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
Et vous changez la mise en œuvre de l' IDisposable.Dipose()
méthode pour:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
et votre outil de finalisation:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Remarque: Si votre objet descend d'une
objet qui implémente la Jeter, puis
n'oubliez pas d'appeler leur base de Disposer de la méthode
lorsque vous remplacez les Éliminer:
public Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Et tous est bon, sauf que vous pouvez faire mieux!
Si l'utilisateur appelle Dispose()
sur votre objet, puis tout a été nettoyé. Plus tard, lorsque le garbage collector vient le long et les appels de Finaliser, il sera alors appel Dispose
de nouveau.
Ce n'est pas seulement inutile, mais si votre objet a ordure des références à des objets que vous avez déjà éliminés depuis le dernier appel à l' Dispose()
, que vous allez essayer de les disposer à nouveau!
Vous remarquerez que dans mon code j'ai eu soin de supprimer les références à des objets que j'ai cédé, je n'ai pas essayer d'appeler Disposer à bord d'une jonque de référence de l'objet. Mais cela n'empêche pas un bug subtil rampant dans.
Lorsque l'utilisateur appelle Dispose()
: la poignée gdiCursorBitmapStreamFileHandle est détruit. Plus tard, lorsque le garbage collector est exécuté, il va tenter de détruire le même handle de nouveau.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle); <--double destroy
...
}
La façon de résoudre ce problème est de dire que le garbage collector qu'il n'a pas besoin de s'embêter à la finalisation de l'objet de ses ressources ont déjà été nettoyés, et pas plus de travail est nécessaire. Vous faites cela en appelant GC.SuppressFinalize()
dans la Dispose()
méthode:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Maintenant que l'utilisateur a appelé Dispose()
, nous avons:
- libéré des ressources non managées
- libéré ressources gérées
Il n'y a pas de point dans le GC de l'exécution de l'outil de finalisation, tout est pris en charge.
Pour répondre à votre question initiale: Pourquoi ne pas libérer de la mémoire maintenant, plutôt que lorsque le GC décide de le faire? J'ai un logiciel de reconnaissance faciale qui besoin de se débarrasser de 530 MO d'images internes maintenant, car elles ne sont plus nécessaires. Quand on n'a pas: la machine broie à une permutation de s'arrêter.
Bonus De Lecture
Pour tout ceux qui aime le style de cette réponse (en expliquant le pourquoi, de sorte que le comment devient évident), je vous conseille de lire le Chapitre Un de Ne de la Boîte de COM Essentiel:
En 35 pages, il explique les problèmes de l'utilisation des objets binaires, et invente COM devant vos yeux. Une fois que vous savez le pourquoi de COM, le restant de 300 pages sont évidentes, et juste détail de Microsoft de la mise en œuvre.
Je pense que chaque programmeur qui a déjà fait affaire avec des objets ou des COM devrait, à tout le moins, de lire le premier chapitre. C'est la meilleure explication de quoi que ce soit jamais.