Why should Dispose() dispose managed resources and finalizer not?
Asked Answered
L

2

12

We all know the System.IDisposable pattern. It's been described a zillion time, also here on StackOverflow:

link: Dispose() for cleaning up managed resources?

The Disposable patterns advises that I should only dispose managed resources if my object is being disposed, not during finalize

You can see that this happens because the following code is advised:

protected void Dispose(bool disposing)
{
    if (disposing)
    {
        // Code to dispose the managed resources of the class
    }
    // Code to dispose the un-managed resources of the class
 }

I know that my class should implement System.IDisposable whenever my class has a (private) member that implements System.IDisposable. The Dispose(bool) should call the Dispose() of the private member if the boolean disposing is true.

Why would it be a problem if the Dispose would be called during a finalize? So why would the following Dispose be a problem if it is called during finalize?

protected void Dispose(bool disposing)
{
    if (myDisposableObject != null)
    {
        myDisposableObject.Dispose();
        myDisposableObject = null;
    }
 }
Languish answered 29/7, 2015 at 14:29 Comment(10)
Finalizers are not guaranteed to execute in a deterministic order. In particular, any of your managed references may have already been finalized before your finalizer gets called.Friable
@DanBryant Not just finalized either - the references your instance has to other objects are no longer binding for the GC, so the managed objects you reference might have already been collected. Even the myDisposableObject != null check isn't enough, since you're in a multi-threaded environment - the reference could be nulled out between the check and the Dispose call. And finally, it's quite likely the finalizer on that object has also been scheduled (and perhaps even finished) already, so it's disposing work is probably done on both the managed and unmanaged side.Carleencarlen
The truth is, you shouldn't need to implement a finalizer yourself anyway. All of the finalizers you need are already written - use safe handles, and you should be fine almost all the time. They might be useful for debugging purposes ("forgot to dispose of this object"), but you rarely need them for what they're designed for - releasing unmanaged resources of the managed object. In your case, the question you're asking already betrays this - you should only care about the unmanaged resources; calling Dispose on another managed object is meddling where you're not supposed to. Don't do that.Carleencarlen
The dispose pattern may have been necessary in .net 1.0. But .net 2.0 included safe handles, so it's almost never useful anymore.Insecure
@CodesInChaos, Agreed; I strongly prefer to make all IDisposable classes sealed and use a simple Dispose method. Even if the class isn't sealed, I just create a protected OnDisposed method and note in the class documentation that derived classes should not implement finalizers. In general, any unmanaged wrappers that actually need a finalizer should be encapsulated into their own sealed class.Friable
So if I understand your comments correctly: even though I holde a reference to one of my objects, the garbage collector could already have finalized it when it enters my destructor?Languish
@Carleencarlen The claim that objects referenced by objects in finalizer queue may already be collected is completely false. The fact that those objects can't get collected is the main reason finalizable objects should be small: everything they reference will get promoted to the next generation if they get put on the finalizer queue. The finalizer is a dangerous place to be in, but the laws of common sense still apply: if you have a reference to something, it still exists in some form. See the Reachability paragraph in this article: blogs.msdn.microsoft.com/cbrumme/2004/02/20/finalizationVent
@Carleencarlen is right: (from Troelses 2010 C#Pro) "A real-world finalizer would do nothing more than free any unmanaged resources and would not interact with other managed objects, even those referenced by the current object, as you can’t assume they are still alive at the point the garbage collector invokes your Finalize() method". Looks strange? But this is the way it works, and in CIL code try catch block finally is inserted to ensure base classes' Finalize() execution, but only preventing user code mistakes.Fishback
Still from the Troelsen book: "When you allocate an object onto the managed heap, the runtime automatically determines whether your object supports a custom Finalize() method. If so, the object is marked as finalizable, and a pointer to this object is stored on an internal queue named the finalization queue". ...Fishback
And also: "When the garbage collector determines it is time to free an object from memory, it examines each entry on the finalization queue and copies the object off the heap to yet another managed structure termed the finalization reachable table (often abbreviated as freachable, and pronounced “eff-reachable”). At this point, a separate thread is spawned to invoke the Finalize() method for each object on the freachable table at the next garbage collection. Given this, it will take at the very least two garbage collections to truly finalize an object."Fishback
E
6

When an object's finalizer runs, one of the following will be true of almost any IDisposable object it might hold:

  1. It held the only reference to that other object, and that other object's finalizer has already run, so there's no need to do anything with it.

  2. It held the only reference to that other object, and that other object's finalizer is scheduled to run, even though it hasn't yet, and there's no need to do anything with it.

  3. Other objects are still using the IDisposable object, in which case the finalizer must not call Dispose.

  4. The other object's Dispose method cannot be safely run from a finalizer threading context (or, more generally, any threading context other than the one where the object was created), in which case the finalizer must not call Dispose.

Even in the cases where none of the above would apply, code which knows that will probably know a lot about its cleanup requirements beyond the fact that it implements IDisposable, and should often use a more detailed cleanup protocol.

Euterpe answered 29/7, 2015 at 16:24 Comment(2)
Do I understand you correctly: If I'm using .NET disposable objects, then I still should implement System.IDisposable and use the Dispose(bool) method from Dispose() and destructor? And in the Dispose(bool) method I should check for the boolean disposing, and ONLY CALL THE DISPOSE OF THE OTHER OBJECTS IF I AM BEING DISPOSED?Languish
@HaraldDutch: Very few objects should have finalizers/C# destructors, and if a base class doesn't have a finalizer but a derived class would need unmanaged cleanup, the derived class should not implement a finalizer itself, but instead keep a reference to a privately-owned object which does. Objects which own IDisposable objects should implement IDisposable themselves, and have Dispose(true) call IDisposable.Dispose on the objects they own, but nothing should ever call Dispose(false).Euterpe
E
4

Generally speaking, you should get rid of resources ASAP. If you don't need a resource why would you keep it around to waste memory?

Also, in order for Dispose() to be called during finalization, you would have to create a finalizer for your object, that is - a destructor in C#. However, the exact time of invoking an object's finalizer is non-deterministic, meaning that your managed objects might not be around / accessible safely anymore at that moment. Even the thread that executes your finalizer is non-deterministic which could also result in problems that are difficult to foresee.

For these reasons finalizers should be created to release unmanaged resources only.

Very few programmers understand 'fully' how finalization works. For instance, it is the garbage collector that recognizes that your type has a finalizer during object instantiation and puts your instance in a special internal data structure called finalizequeue. Indeed when you debug your application with sos.dll (windbg) you can use the command !finalizeQueue to display the objects that have finalizers and are yet to be finalized at some later point in time.

Emplace answered 29/7, 2015 at 14:44 Comment(1)
It's not true that your managed objects might not be around/accessible safely anymore at the time of finalization. It's just that their finalizers might have been called already, if they exist.Vent

© 2022 - 2024 — McMap. All rights reserved.