Lea Hayes
Lea Hayes

Reputation: 64196

How to properly dispose collection of unmanaged resources from finalizer?

Here is an example about which I am uncertain:

public class SomeClass : IDisposable {

    ~SomeClass() {
        Dispose(false);
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing) {
        if (!_disposed) {
            if (disposing) {
                // TODO: Release any managed resources here...
            }

            // ?! Is it safe to enumerate the dictionary here ?!
            foreach (var resource in _resources.Values)
                ReleaseBuffer(resource);
            _resources = null;

            _disposed = true;
        }
    }

   private Dictionary<string, IntPtr> _resources;

    ...

}

Will it be safe to enumerate the managed dictionary in order to release the unmanaged resources?

Is availability of the dictionary uncertain since the order in which finalizers are invoked is not defined?

Here is a quote taken from the MSDN which I find confusing [1]:

  • The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.
  1. http://msdn.microsoft.com/en-us/library/system.object.finalize(v=vs.110).aspx

Upvotes: 3

Views: 637

Answers (2)

supercat
supercat

Reputation: 81115

Rather than having a dictionary of unmanaged resources, I would suggest having a dictionary of independent wrapper objects, each of which is responsible for guarding one unmanaged resource. If the object holding the dictionary is abandoned and no other references exist to the wrapper objects, all of the wrapper objects will get finalized without needing to involve the dictionary itself in the process. Using such an approach will make it easier to sanely handle cases in which an exception occurs during object construction, and at least somewhat-sanely deal with situations where an object finds itself resurrected between the time it has been enqueued for finalization and the time the finalizer runs [code generally can't be expected to run "correctly" in such cases, but should avoid corrupting the state of the rest of the system].

For example, code which uses a handle may acquire a lock during its use and, after use, check a "disposeObjectASAP" flag; if set, re-acquire the lock and dispose object. The finalizer itself should set the flag and then try to acquire the lock; if it successfully acquires the lock, it should dispose the object. If unable, the fact that it set the flag should imply that code which has the lock is destined to check the flag and clean up the object, so the finalizer doesn't have to. If the finalizer runs prematurely, it may release resources which another thread is going to need, causing actions on that other thread to fail, but the finalizer won't release resources while another thread is using them or disposing them, since releasing resources in those situations could cause massive system corruption.

Upvotes: 2

Jim Mischel
Jim Mischel

Reputation: 133950

According to Implementing a Dispose method, the code you show there is not safe.

The code example shows:

protected virtual void Dispose(bool disposing)
{
   if (disposed)
      return; 

   if (disposing) {
      // Free any other managed objects here. 
      //
   }

   // Free any unmanaged objects here. 
   //
   disposed = true;
}

Your sample shows you freeing unmanaged resources in the conditional block. The MSDN example shows that you should free managed resources in the conditional block.

As it says in the text (under the heading, "The Dispose(Boolean) overload"):

If the method call comes from a finalizer (that is, if disposing is false), only the code that frees unmanaged resources executes. Because the order in which the garbage collector destroys managed objects during finalization is not defined, calling this Dispose overload with a value of false prevents the finalizer from trying to release managed resources that may have already been reclaimed.

Upvotes: 1

Related Questions