A few questions and answers about using IDisposable
I wrote a little bit on Disposable objects and their uses in Chapter 5 of the Performance and Scalability PAG (look here and the following sections) and it triggered a lively discussion here at MS the other day. Here’s the bit of interest and some of the discussion reduced to Q&A form:
“The reason you want to avoid finalization is because it is performed asynchronously and unmanaged resources might not be freed in a timely fashion. This is especially important for large and expensive unmanaged resources such as bitmaps or database connections. In these cases, the classic style of explicitly releasing your resources is preferred (using the IDisposable interface and providing a Dispose method). With this approach, resources are reclaimed as soon as the consumer calls Dispose and the object need not be queued for finalization. Statistically, what you want to see is that almost all of your finalizable objects are being disposed and not finalized. The finalizer should only be your backup.”
Q: The whole point of GC is to manage the lifetime of objects in a sensible way. Doesn’t this “guideline” (using IDisposable) suggest that objects in certain categories (with attached unmanaged resources of any kind) cannot participate in GC – and we require the client to manage it outside the GC apparatus?
A: Yes that is basically correct. Finalizable objects generally require the client to take additional action, such as wrapping their use in a "using" statement. If the client does not do this they will get sub-optimal performance. It is not mandatory, however, so basically what you want to do is Dispose all the ones that can be readily Disposed because they have easily understood lifetime and let the GC handle just the exceptions, which hopefully are much more rare.
Q: Doesn’t this make it much harder to use classes that have a finalizer?
A: That has not been my experience. I have found that generally the developer that creates the class cannot say what the lifetime of instances will be but that it is most often a simple lifetime and that they should therefore provide a facility (Dispose) which allows the customer of their class to take advantage of the common case and get superior performance.
Q: Consider the case where the managed current implementation has an underlying unmanaged implementation, doesn’t that lead to you a design where there are unmanaged resources attached to virtually all objects?
A: This is an exceedingly bad situation to be in. If you truly have finalizable state in all your objects you will swamp the finalizer thread and give the GC fits because all your objects will be living at least one generation longer than they otherwise would have had to live. I would urge you to find a way to consolidate the unmanaged state into comparatively few objects which can then be recycled like (e.g.) database connections or something of that ilk. If your objects are, on the other hand, not transient then you may be ok because recovery of the unmanaged resources is a rare event in any case.
The use of Dispose pattern is not advice that is lightly given. You may be very disappointed with the performance you get if you follow another pattern and are not very careful.
Comments
- Anonymous
May 19, 2004
The topic of Finalization and Dispose gets complicated pretty quickly - there are lots of variations. One of the problems we've run into is that you can't really get very far without using some underlying system resource or legacy API (e.g. COM) that has destructor semantics (Open/Close, or Create/Destroy) associated with it.
We've gotten to the point where we have our own internal guidelines for classes that need either a finalizer or a Dispose.
1. If you need either one then you must implement both.
2. The Dispose method calls GC.SuppressFinalize(this) after performing the dispose operation. We usually provide a method Dispose(bool disposing) for this; it seems to be almost a standard approach anyway.
3. The finalizer calls the same dispose method.
4. Clients consuming a class with a Dispose method are required to treat it as part of the contract; if it has a Dispose() method it must call it when the object is no longer needed.
5. In the debug build the finalizer will Assert, throw an exception, print to the debug port, etc., so that developers are made aware that they created an object that they did not properly dispose of and for which they did not honor the Dispose contract. We don't have a guideline yet for the release build, but I tend toward thinking that using a Trace statement is useful to provide breadcrumbs, on the expectation that it ought to happen rarely.
6. Minimize the number of classes that need either a finalizer or a Dispose.
As with all such rules, these are guidelines only - special cases require special handling. - Anonymous
May 19, 2004
I'd agree with that but call GC.SuppressFinalize first, before doing anything else. I've actually seen (on Compact Framework) a finalizer be called when in the middle of executing that object's Dispose method. I can only assume that the JIT stopped reporting the reference and a collection happened, causing the object to get onto the freachable queue. - Anonymous
May 20, 2004
David writes:
>> 1. If you need either one then you must implement both.
If you have a finalizer you should have Dispose but if you have Dispose you don't necessarily need to be finalizable. Consider an object has finalizable members. It need not itself be finalizable because should it die its sub-objects go into the finalization queue directly. However it should be Disposable so that when Disposed it can in turn call dispose on its members.
Re: 2&3. There's a recommended pattern for Dispose methods that covers these. An example is in the PAG (see link in main article).
Re: 5. That's a great defensive mechanism but not always possible with objects that have complex lifetime. I like the Trace idea, it should be rare enough that it doesn't cause a problem at that level.
Re: 6. Amen. - Anonymous
May 20, 2004
Mike,
The argument I've heard in favor of calling GC after disposing the object is that if the dispose throws an exception the call to the GC will never be made so you will get a 2nd chance to cleanup on the finalize thread. This never made much sense because if it threw the 1st time, when other managed objects were still valid, there would be an even greater chance it would throw the 2nd time (on the finalizer thread) when there was no guarantee that managed objects were still valid. But all the examples I've seen from MS show it done this way.
The issue of thread safety is a different concern. If the object can be disposed by two different threads (not the finalizer) then the disposed object itself should handle the threading issues to prevent multiple simultaneous calls to Dispose from causing problems. If its because the Dispose method may get called from an object at the same time the finalizer thread disposes the object, then a fix may be very simple.
In the dispose method add a flag that is used like this...
Dispose()
{
if ( !disposed )
{
DoSomeCleanup();
this._disposed = true; // set a instance field
}
}
This should cause the JIT to report a reference to the GC preventing it from getting finalized until the _disposed flag has been set. It still isn't thread safe but it should prevent it from being put on the freachable queue prematurely. - Anonymous
May 20, 2004
Brad,
re: 1) Dispose but no finalizer
I agree that you don't always need a finalizer but I would argue that it probably should have one if it has a Dispose. I contend that Dispose should be treated as a contract requirement and that not invoking it should be treated as an error. The finalizer provides a mechanism that allows us to catch these omissions. It may be that it isn't strictly neccessary and may add overhead, but if the object's contract is honored the object will never get put on the freachable queue anyway.
I would also argue that users of a class should not have incestuous knowledge of its inner workings - they should not "know" that it's ok to not invoke the finalizer. It's implementation can change at any time and invalidate those assumptions.
But in the end, it's only a best practices guideline, not a requirement.
regards, - Anonymous
May 21, 2004
Dispose()
{
if ( !disposed )
{
DoSomeCleanup();
this._disposed = true; // set a instance field
}
}
This sort of thing isn't necessary the "this" pointer is necessarily live while any member is running including DoSomeCleanup -- all manner of disasters would ensue if that was not the case.
Sometimes people do see finalization happening concurrently with disposing. This isn't a normal situation but it is possible with exotic kinds of cleanup -- people often call the standard Dispose of another object in their finalizer -- this is generally a bad idea because the object may itself have already been finalized though it is still live via your object.
Remember all the object members of a finalizable object remain live but finalization order is not guaranteed so your members may have already been disposed or finalized. It's best to ignore them entirely in your finalizer and only release unmanaged state you directly own.
You avoid this problem entirely if you follow my guideline of never having a finalizable object have any state other than the unmanaged state which it owns (i.e. make it a leaf object).
When Disposing this is not the case, you should Dispose your members if any. Keeping in mind that if you have members to dispose, and you're following my guideline, then all you are doing is holding disposable objects and you yourself would not be finalizable because you have no unmanaged state of your own to clean up.
There is more detail on this in the PAG at this link
http://msdn.microsoft.com/library/en-us/dnpag/html/scalenetchapt05.asp?frame=true#scalenetchapt05_topic13 - Anonymous
May 21, 2004
Is there any way to detect if a developer forgot to call Dispose on an object that implements IDisposable without having a finalizer as someone else suggested? Can FXCop do this? - Anonymous
May 21, 2004
Can't do it in general via static analysis (ala FXCop) but maybe some of the more common cases could be handled that way. - Anonymous
May 22, 2004
The comment has been removed - Anonymous
May 25, 2004
I'll talk to Chris about it, I think it's worth getting a definitive answer out there.
I can say this much though, and this is an important special case.
If you write this (as you wrote above):
new SomeObject();
The code does not have the new object pointer until after the constructor has finished running. Whether you then store it afterwords or not cannot make a difference as to what happened during construction. If the JIT reported a member variable or local of your method as a live root that would be all fine and well but that root couldn't possibly be holding the value during the construction anyway because it doesn't yet have the new object reference.
So either the constructor itself, or else the CLR in the context of creating the object must necessarily keep the object alive otherwise if a colletion was triggered your object would go away.
Speaking very broadly, there are many complex lifetime issues like this and if you had to think about any of them the whole model would be a disaster. It has to "just work".
Now you can imagine cases where even though a method was running that the "this" pointer is no longer reachable. If there is truly such a case then collection is perfectly fine, you can't get to the object anymore anyway so it's impossible to know if it's gone as a practical matter anyway. If someone else can access the object then by definition its still reachable and it won't be discarded even though the member can't reach it.
I do not see the point of code like this:
DoSomeCleanup();
this._disposed = true; // set a instance field
Either DoSomeCleanup() needed the this pointer or it didn't. As long as it needed it then it would be live. The normal lifetime management will do the job just fine. - Anonymous
May 25, 2004
The comment has been removed - Anonymous
May 27, 2004
The comment has been removed - Anonymous
May 28, 2004
This gets curiouser and curiouser.
I think there's a problem here but I'm going to have to do more digging to be sure. I don't think the debug version of the code enters into it. I can make the debug version exhibit the same behaviour.
I changed your test case a little bit to force the issue and to illustrate that storing the reference makes no difference as it is by then far too late.
Here is the altered version:
using System;
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("Creating object");
MainClass mc = new MainClass(); // this doesn't save you from getting a finalized object (!?!)
Console.ReadLine();
}
public MainClass()
{
Console.WriteLine("MainClass.ctor calling collect");
GC.Collect();
Console.ReadLine();
Console.WriteLine("MainClass.ctor done");
}
~MainClass()
{
Console.WriteLine("MainClass Finalizer");
}
} // MainClass - Anonymous
May 28, 2004
The comment has been removed - Anonymous
May 31, 2004
The comment has been removed - Anonymous
June 03, 2004
The comment has been removed - Anonymous
June 07, 2004
The comment has been removed - Anonymous
June 14, 2004
What happens if we call Dispose multiple times on an object.
I tried it on SqlConnection and no exception etc was thrown.
the reason I ask is b'cos I have written a wrapper for an object which provides dispose method and expose that object as a property.
Now developer can call dispose method on underlying object.
Also I implemented Dispose method and finalizer, so that if developer forgets to call dispose method on underlying methods then it gets cleaned up.
So what are the performance implications. - Anonymous
July 03, 2004
Two quick hrefs