Udostępnij za pośrednictwem


Demystifying Dispose

One commonly misunderstood feature is the relationship between disposable objects (Objects that implement the IDisposable interface) and the GC.  I know there are a lot of online resources about patterns and best practices but there is still a lot of confusion.  I remember going to a Dev Lab to answer some questions by Whidbey early adopters, and being asked at what point in an object’s lifetime does the GC call Dispose.  The answer: Never.

Dispose is just a method, like any other.  Its purpose is allow deterministic resource cleanup, much like C++’s destructors, but without freeing the object’s memory.  Like a C++ destructor, the developer puts whatever cleanup code is necessary to be performed when the object’s lifetime is up.  Unlike a destructor, the object lives on after being "disposed of".  This means an object may be in an invalid state after being disposed, and developers should consider using ObjectDisposedExceptions when methods in a disposed object are called.

What happens when you call Dispose()

Any cleanup your object need done in a timely fashion should be done in Dispose.  Things like closing database connections, closing files, releasing bitmaps, etc.  Unmanaged resources in particular should be released in Dispose.  This is because the GC has no knowledge of anything not allocated on the managed heap.  For example, a Bitmap object that encapsulates a 2MB image file reports only its managed size (the size of the managed object) to the GC.  The GC knows nothing about the 2MB unmanaged image, thus will not collect it from memory.  By calling Dispose, you tell the Bitmap object that you are finished with it, and it will release the image itself.

What doesn’t happen when you call Dispose()

  • Calling Dispose does not prioritize the object for garbage collection. It simply unloads the object’s (unmanaged) resources from memory.
  • Calling Dispose does not deallocate the object from memory.  Only the GC does that when it performs a collection of the generation in which the object resides.
  • The CLR does not insert or run any code not in Dispose.  The behaviour of Dispose is defined by the developer.
  • Dispose must be called explicitly by the application.  It is never called by the runtime.  The only exceptions are when using C#’s using statement (see ShawnFa’s article for a good explanation of what exactly goes on), and the foreach keyword in C# will result in Dispose being called on the Enumerator
  • Dispose is NOT threadsafe.  This means two threads can call Dispose on the same object at the same time.  Like for any other synchronization-sensitive method, take steps to make sure this doesn’t happen.  I’ll give an example of when this might happen in the next section.

What about the finalizer?

There are a few reasons why not to rely on the finalizer to clean up resources that I’ll cover in a future blog entry on finalizers.  The main reasons you should be concerned with are performance and determinism.  Finalizers are expensive to run, and there’s no order (or even guarantee) that they will be run (for example, the finalizer thread may time out, or be killed on AppDomain unload).

That being said, the finalizer should be your last chance to clean up resources, in case someone using your class forgets to dispose of it when done.  A call to Dispose inside the finalizer reduces duplication of code.  However, you want to make sure the resources don’t get released twice.   In your Dispose method, you want to make sure you call GC.SuppressFinalize(this) after your clean up code.  If you suppress the finalizer before the clean up, you’re limiting your ability to recover from failures during the cleanup.

Some of you may have noticed a race condition in my description above.  Consider a situation where the call to Dispose is the last time your object is being referenced.  As soon as Dispose is entered, the object is eligible for collection by the GC.  Before that happens, the object’s finalizer gets called, which calls Dispose.  You now have two threads inside Dispose, possibly double-freeing resources (this could be bad, as in the case of GCHandles).  Make sure you follow the pattern I linked to above and use a disposing flag to avoid problems like this.

For more information about an object being collected while one of its methods are being run, check out these (very complete) blog posts by Chris Brumme: (Finalization and Lifetime, GC.KeepAlive, handle recycling).

If your .NET application is using unmanaged resources, make sure to implement the Dispose pattern, secure in the knowledge that nothing magical is going on.

EDIT: Added point about Dispose and foreach. Thanks Steve!

Edit:   Minor corrections.

Comments

  • Anonymous
    September 21, 2004
    How come an object can be finalized while my code is still holding a reference to it and executing a method (Dispose()) on that reference? If that's true then there's a major bug in the GC.
  • Anonymous
    September 21, 2004
    The comment has been removed
  • Anonymous
    September 21, 2004
    Your first point,

    " It simply unloads the object’s (unmanaged) resources from memory."

    Surely that's not true. The way that reads (to me) is the unloading is automatic, when it's actually down to the code within the dispose implementation.
  • Anonymous
    September 21, 2004
    Hi Barry,

    Dispose does whatever you put in it. It's meant to clean up unmanaged resources, whether it be unloading bitmaps from memory or closing sockets. You're right, there's nothing "automatic" about it. Sorry if that sounds confusing.
  • Anonymous
    September 21, 2004
    ~finalizer() is automatic, correct? I want finalizer() to call Dispose() to make sure the database connection has been closed but I get errors.

    Can anyone point me to a resource that might help me accomplish this? Example maybe?

    Good topic to blog.
  • Anonymous
    September 21, 2004
    Hi Darin,

    The Dispose Pattern is probably what you're looking for: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconfinalizedispose.asp

  • Anonymous
    September 21, 2004
    I think most of the confusion around Dispose is because of the lack of good documentation on it - most users see the System.Windows.Forms.Control class and how it's finalizer calls the special void Dispose(bool disposing) method. I did eventually figure it out, but when you start it's fairly confusing - the documentation is geared more towards a very advanced developer who also understands how the GC works.

    In your next article I'd really like to see a complete list of the times where the Finalizer is not called. I can't find the information anywhere - the MSDN documentation seems to imply that it randomly isn't called during normal usage - when my hunch is that it's a not a very common thing. Having the Finalized not guarunteed is a flaw in .NET, IMHO - if you don't clean up unmanaged resources - you have a memory leak. When the documentation basically says "oh well" it kind of makes developers cranky :-)

    I'm also surprised that not many developers discuss HandleRef and when it should be used. In which cases should I use HandleRef as opposed to IntPtr when calling unmanaged APIs?
  • Anonymous
    September 21, 2004
    Chris, that's what I thought. In that case your blurp about synchronizing Dispose() when you call it from the finalizer is pointless. Either your code will call Dispose() explicitly, and in that case the finalizer will not run since you're still holding a reference or Dispose() will be called from the finalizer, at which point nobody holds a reference to call it explicitly and since you said the runtime won't call it for you there's no way anybody else could call it at the same time.

    There's still nothing to stop the user from calling Dispose() twice but then, most of the standard class library is not thread safe either.
  • Anonymous
    September 21, 2004
    The comment has been removed
  • Anonymous
    September 21, 2004
    The comment has been removed
  • Anonymous
    September 21, 2004
    Chris Brumme explains it, and it does not say that the object is callable during finalization. He does go into details and all he says that Dispose can be called multiple times from different threads (which I meantioned and which should really be taken care of the app, not your object) or that finalizer can be called while the resource is still used by other code, detached from the original object - which won't be solved by synchronizing Dispose() anyways.

    My point is that you should not worry about Dispose being called twice at the same time (not twice at different times), unless you want to make your object completely thread safe. And the vast majority of objects, including in the BCL are not and do not have to be thread safe.
  • Anonymous
    September 21, 2004
    Chris says if you resurrect your object "...application threads and the finalizer thread can simultaneously be active in your object". This unfortunately includes calling the Dispose method.

    You shouldn't have to worry about this situation unless you're planning to resurrect objects. Like you said, the BCL does not do this, because in many cases, a resurrected object is not in a valid state.

    To make a long story short (too late):
    -Don't use finalizers
    -If you have to, don't resurrect
    -If you have to, consider making your object's methods thread safe
  • Anonymous
    September 21, 2004
    Cbrumme didn't go into much detail, but how does one resurrect objects?
  • Anonymous
    September 21, 2004
    If you assign a live reference to an object's this pointer in the finalizer, the object will be taken off, the put back on the finalization queue (or if you call GC.ReregisterForFinalize() on an object).

    I'll save a discussion of resurrection for another blog entry.
  • Anonymous
    September 21, 2004
    The comment has been removed
  • Anonymous
    September 21, 2004
    "...and the foreach keyword in C# will result in Dispose being called on the enumerated object if the IEnumerator-implementing class that's being enumerated implements IDisposable."

    Really? I didn't know that... is there any way to suppress this behavior?
  • Anonymous
    September 21, 2004
    Nice link Anon!

    FYI, here's the newsgroup thread that spawned Valerie's post:
    http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=Osr50MDCEHA.1588%40tk2msftngp13.phx.gbl&rnum=1&prev=/groups%3Fq%3Dbrumme%2Bthread%2Bdispose%26hl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26selm%3DOsr50MDCEHA.1588%2540tk2msftngp13.phx.gbl%26rn
  • Anonymous
    September 21, 2004
    Matt, to be clear, it's the Enumerator that's being Disposed, not the elements of the Enumeration:
    http://weblogs.asp.net/brada/archive/2004/06/25/166527.aspx
  • Anonymous
    September 28, 2004
    S dot One heeft het over .NET » Uitleg over Dispose
  • Anonymous
    October 02, 2004
    Hi Chris

    BEGIN QUOTE
    However, an object is eligible for collection after the last reference to that object, which may be inside one of the object's methods.
    END QUOTE

    I understand that GC collects object not referenced directly or indirectly by a root (global/static variables, local or parameter variables on thread stacks, the cpu registers that refers to reference type objects). The 'this' pointer has to be either on the stack or in a CPU register, so it is considered a root. Am I correct?
  • Anonymous
    May 20, 2005
    After reading a bunch of different articles and implementations I have finally come to terms with the...
  • Anonymous
    July 21, 2005
    The comment has been removed
  • Anonymous
    February 21, 2006
    After reading a bunch of different articles and implementations I have finally come to terms with the...
  • Anonymous
    May 02, 2006
    The comment has been removed
  • Anonymous
    May 02, 2006
    Dr. Evil,
    As far as I know, the VB compiler did not change the Dispose/Finalizer symantics for VB.NET 2005.  Please point me to any references that claim this so I can investigate.

    Thanks
    -Chris
  • Anonymous
    July 23, 2006
    If "Dispose is just a method, like any other." and needs to be called explicitly why do we need to inherit from IDisposable interface? We could very well write our own method and call that explicitly from application and finalizer. Does this has to do something with using{...}?

    Please advice.

    Thanks
    Rohit
  • Anonymous
    July 24, 2006
    Hi Rohit

    There are a few reasons why you would want to inherit from IDisposable.  As you pointed out, the using block requires that your object implement it.  Another good reason is that consumers of your class can find out in an easy and uniform way whether your object exposes such a method by attempting to cast as IDisposable.  Imagine having an ArrayList of objects and some of which need to be disposed.  Using the "as" C# keyword will allow you to easily dispose of the objects that need it.

    Hope that helps.
    -Chris
  • Anonymous
    April 28, 2008
    So in a previous post, we talked about Understanding when to use a Finalizer in your .NET class so now
  • Anonymous
    January 21, 2009
    PingBack from http://www.keyongtech.com/442901-suppress-finalize-in-dataset-constructor