Welcome to MSDN Blogs Sign in | Join | Help

Finalization revisited

Last time (http://blogs.msdn.com/andrewdownum/archive/2004/12/13/282135.aspx) I talked about the consumer side of the dispose pattern (i.e. how to correctly use an object which supports the IDisposable pattern to ensure that its non-memory resources are cleaned up promptly).  This time, we get to talk about the producer (that is the definition of a disposable object itself).

 

Say that you are developing an object which will wrap an unmanaged resource (like a database connection, a file handle, a network socket or anything which has a greater significance than its memory space implies).  In this case you need to ensure that this resource gets freed up when it is no longer in use.  The simple way to do this is to provide a Dispose() method which frees the resources when called (either explicitly, or indirectly via the using statement)

 

public class DisposableObject : IDisposable

{

      private bool disposed = false;

      public void Dispose()

      {

            if (!disposed)

            {

                  disposed = true;

                  CleanupNativeResources();

            }

      }

      protected virtual void CleanupNativeResources()

      { /*Do some interesting work here*/ }

}

 

The class above implements the IDisposable interface and when disposed, it will call CleanupNativeResources(), a virtual method which is assumed to actually implement whatever cleanup logic is required (for example calling a Win32 API to close a file handle).

 

But what happens if the consumer of this object forgets to call dispose.  Even after all the warning given that it is the consumer’s responsibility to call the Dispose() method, sometimes these objects never get properly disposed.

 

Enter Finalization.

 

Finalization is a process through which an object can be registered with the runtime to have a special finalize method which will be called at some point after it can be guaranteed that there are no longer any references to the object.  As mentioned earlier, this could happen at any time after there are no references remaining to the object (this could be a long time if the Garbage Collector doesn’t detect any reason it needs to collect) but it makes for a good last chance to clean up these resources.

 

public class FinalizableObject : IDisposable

{

      private bool disposed = false;

 

      public void Dispose()

      {

            if (!disposed)

            {

                  disposed = true;

                  CleanupNativeResources();

                  GC.SuppressFinalize(this);

            }

      }

 

      ~FinalizableObject()

      { Dispose(); }

 

      protected virtual void CleanupNativeResources()

      { /*Do some interesting work here*/ }

}

 

This latest snipped adds a finalizer method (in C# this uses the syntax of a method ~%ClassName%() where you would replace %ClassName% with the actual name of the class you are defining a finalizer for).  In this case the finalizer simply calls the dispose method (which is generally a good idea).

 

There is also a call inside of the dispose method to GC.SuppressFinalize(this);

which notifies the runtime that the object no longer requires finalization and can be collected normally.  This avoids the performance hit incurred by having a finalizer if it is not needed.

 

Now we can write a class which can be disposed by a caller that no longer needs it, and which will be disposed by the runtime automatically at some later date if that doesn’t happen, but we would like to encourage the consumer to use dispose functionality and to notify them when they are not doing so.

 

We can do this in a very similar way to what Visual C++ does for memory leaks.  We simply keep track of all of the objects which are finalized (i.e. weren’t properly disposed) and then log some kind of information for them.

 

Following is the pattern that I use:

 

public class FinalizableObject : IDisposable

{

#if DEBUG

      private System.Diagnostics.StackTrace allocStack;

#endif //DEBUG

#if THREADSAFE

      //Use an int here so that we can later use Interlocked.CompareExchange

      private int disposed = DISPOSED_FALSE;

      private const int DISPOSED_TRUE = 1;

      private const int DISPOSED_FALSE = 0;

#else //!THREADSAFE

      private bool disposed = false;

#endif //THREADSAFE

 

      public bool Disposed

      {

            get

            {

#if THREADSAFE

                  return DISPOSED_TRUE == disposed;

#else //!THREADSAFE

                  return disposed;

#endif //THREADSAFE

            }

      }

 

      public FinalizableObject()

      {

#if DEBUG

            allocStack = new System.Diagnostics.StackTrace();

#endif //DEBUG

      }

 

      public void Dispose()

      {

#if THREADSAFE

            if(DISPOSED_FALSE == System.Threading.Interlocked.Exchange(ref disposed, DISPOSED_TRUE))

            {

#else //!THREADSAFE

            if (!disposed)

            {

                  disposed = true;

#endif //THREADSAFE

                  CleanupNativeResources();

                  GC.SuppressFinalize(this);

            }

      }

 

      ~FinalizableObject()

      {

#if DEBUG

            System.Diagnostics.Debug.Assert(false, "FinalizableObject was not disposed" + allocStack.ToString());

#endif //DEBUG

            Dispose();

      }

 

      protected virtual void CleanupNativeResources()

      { /*Do some interesting work here*/ }

}

 

Notice that if the debug compilation flag is set I capture the stack trace when the object is allocated and then fire a debug assertion in the finalizer that complains about the object being finalized.  The assert then has enough information to figure out where the object was allocated and why it wasn’t released.

 

This code also optionally supports threadsafe handling (so that dispose can be called concurrently by multiple threads.

 

Hope this snipped helps someone out there who is struggling with finalization.

Published Tuesday, January 04, 2005 11:44 AM by AndrewDownum

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Finalization revisited

Tuesday, January 04, 2005 12:09 PM by thomas woelfer
Andrew,

thanks, that was quite helpful.

WM_CHEERS
thomas woelfer

# re: Finalization revisited

Tuesday, January 04, 2005 2:43 PM by David M. Kean
What happens if you want to dispose of managed resources? Your pattern doesn't allow for this.

I have written a similar entry at the following address:

http://davidkean.net/archive/2004/09/30/172.aspx

However I like the idea of capturing the stack trace and the thread safe implementation, which mine does not implement. I will update mine to include them.

# re: Finalization revisited

Tuesday, January 04, 2005 3:10 PM by Andrew Downum
I'll get into disposing of managed resources in a bit, but to answer your question about disposing managed resources, I think it is generally an error to have a finalizable object which contains references to disposable objects. The inherent problem here is that a finalizable object will potentially prolong the lifetimes of its entire reference graph and therefore should be kept out at the leaves of an allocation tree.

Ideally a finalizable object will contain only a single unmanaged resource and no references to other managed objects. It would then be wrapped in a disposable (but not finalizable) object to allow it to be deterministically disposed.

# re: Finalization revisited

Tuesday, January 04, 2005 8:26 PM by David M. Kean
I don't agree.

If you follow the IDisposable pattern (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconFinalizeDispose.asp) and null out any managed references in your Dispose method you won't keep any references around once Dispose has been called.

For example:

~FinalizableObject()
{
Dispose(false);
}

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

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_MyManagedObject != null)
{
_MyManagedObject.Dispose();
_MyManagedObject = null;
}
}

if (!Environment.HasShutdownStarted)
{
// Release unmanaged objects
}
}

# re: Finalization revisited

Wednesday, January 05, 2005 10:32 AM by Andrew Downum
The only problem with this is when your finalizable/disposable object is not properly disposed (yes, it _should_ always be disposed but ideally we handle the case where it is not). In this case when the object is added to the finalization queue, the lifetimes of all of the objects which it references (including the other disposable objects) are extended and they will survive the first garbage collection.

This is sub-optimal. It is better for this reason to avoid references to managed at all from finalizable objects.

# re: Finalization revisited

Wednesday, January 05, 2005 10:40 AM by Andrew Downum
Just one further nit.

Your finalizer should be releasing unmanaged resources regardless of Environment.HasShutdownStarted. This could be true in the case where an AppDomain is being unloaded, but not the entire process and thus the OS will not be cleaning up for you (also it is important for other resources which may not be automatically reclaimed by the OS on a process exit).

What HasShotdownStarted indicates is whether or not it is safe to reference static objects.

# re: Finalization revisited

Wednesday, January 05, 2005 6:09 PM by David M. Kean
If you have an object that implements IDisposable and Dispose is not called, then you are going to have issues and I think keeping around managed references is going to the be least of your problems. It is the responsibility of the user of the object to call Dispose, and if they don't then any memory issues they have are their own responsibility.

With HasShutdownStarted, how do you get rid of unmanaged resources? You would typically have to call a static pinvoke method, and if HasShutdownStarted is true, then you cannot safely reference a static method to get rid of the unmanaged resource. I'm not sure whether it is safe to reference a static method within the same class of an instance that is being finalized, I will check the C# specs when I get into work.

# re: Finalization revisited

Thursday, January 06, 2005 10:21 AM by Andrew Downum
Yes I agree that it is always better to call dispose when it is available, but if we were prepared to assume that Dispose would _always_ be called then there would be no point whatsoever in finalization. Finalization exists solely as a way to somewhat gracefully recover from the case where dispose is not called.

Calling static _methods_ is fine in and of itself, the executable code has not been removed at the point when HasShutdownStarted returns true. Basically what HasShutdownStarted means is that the GC is performing a collection where it ignores all of it's rooted references. That means that you cannot assume any order in which finalizers will be run and you have a reference to a static object it may have already been finalized. The memory will still be there, but it is not guaranteed to be in a valid state (because it may have been finalized).

This is another good reason to ensure that finalizable objects contain no managed references. This way you don't even have to worry about the HasShutdownStarted case, simply release the single unmanaged resource wrapped by the object and you are done.

# online directory main

Thursday, April 06, 2006 11:51 AM by online directory main
Welcome!!! http://www.dirare.com/Sweden/">http://www.dirare.com/Sweden/ online directory. [URL=http://www.dirare.com]YP national[/URL]: SMART Yellow Pages, About DIRare, Search in Business Category. Also [url=http://www.dirare.com]global directory[/url] from online directory .

# online directory main

Thursday, April 06, 2006 11:51 AM by online directory main
hello! http://www.dirare.com/Sweden/ online directory. SMART Yellow Pages, About DIRare, Search in Business Category. From online directory .

# 一般人とは違う価値観

Saturday, June 13, 2009 4:17 AM by セフレ

女性のオナニーをお手伝いして、謝礼をもらうお仕事を始めませんか?

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker