Certainly we should call Dispose() on IDisposable objects as soon as we don't need them (which is often merely the scope of a "using" statement). If we don't take that precaution then bad things, from subtle to show-stopping, might happen.
But what about "the last moment" before process termination? If your IDisposables have not been explicitly disposed by that point in time, isn't it true that it no longer matters? I ask because unmanaged resources, beneath the CLR, are represented by kernel objects - and the win32 process termination will free all unmanaged resources / kernel objects anyway. Said differently, no resources will remain "leaked" after the process terminates (regardless if Dispose() was called on lingering IDisposables).
Can anyone think of a case where process termination would still leave a leaked resource, simply because Dispose() was not explicitly called on one or more IDisposables?
Please do not misunderstand this question: I am not trying to justify ignoring IDisposables. The question is just technical-theoretical.
EDIT: And what about mono running on Linux? Is process termination there just as "reliable" at cleaning up unmanaged "leaks?"
LATE EDIT: Although "other uses" may exist for IDisposables, my focus is strictly on resource leaks. I've heard two answers: (1) if your process refuses to terminate, you will have a leak and (2) yes, resources can leak even if the process terminates. I certainly agree with item (1), though it is just outside the scope of what I'm after. Otherwise, item (2) is exactly what i'm looking for, but I can't shake the feeling it is just a guess. Jeffrey Richter ("Windows via C/C++") explains that a (gracefully) terminated Win32 process will not leave leaked or orphaned resources. Why would a process containing the CLR change that? Where is the documentation, specific example, or theoretical scenario that gives credance to the idea that the Win32 process clean开发者_运维问答up capability is compromised when using the CLR?
Technically speaking, it all depends on what the IDisposable does. It has been used for a lot of things, not just unmanaged resources.
For example, when working on an Outlook application I had built a nice little abstraction of the Outlook API. Attachments were particularly annoying to work with as streams because you needed to save it out to a temp file, work with it, then clean it up.
So my abstraction looked something like this:
OutlookMailItem mailItem = blah;
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) {
// work with stream
}
When Dispose was called on the AttachmentStream, the temp file it was based on was deleted. In this case, if Dispose was not called, the temp file would never be cleaned up. I had a process at startup to look for these orphaned files but I figured I'd present this as an example.
In reality, nearly all IDisposable implementations which wrap some kind of socket, handle, or transaction will simply be cleaned up by the operating system upon process termination. But obviously that's like welfare. Avoid it if you can.
During a cooperative shutdown, the AppDomain is unloaded, which causes all finalizers to execute:
From Object.Finalize documentation:
During shutdown of an application domain, Finalize is automatically called on objects that are not exempt from finalization, even those that are still accessible.
So you're safe on shutdown as long as two criteria are met:
Every
IDisposable
object that's still alive has a correctly-implemented finalizer (true of Framework classes, may not be true of less-trustworthy libraries); andIt's actually a cooperative shutdown, and not an abnormal shutdown such as a hard process termination, Ctrl-C in a console app, or
Environment.FailFast
.
If either of these two criteria aren't met, it is possible that your application is holding onto global unmanaged resources (such as a mutex), which actually will leak. Therefore, it's always better to call Dispose
early if you can. Most of the time, you can rely on the CLR and object finalizers to do this work for you, but better to be safe than sorry.
One thing that I often find myself disposing is serial ports-- now, while a serial port should be freed when the program lets up, other programs can't access the serial port while it's being held by another process. So, if your process refuses to die, then you're tying up a serial port. That can be really bad if the user tries to restart your program, but a zombie version of the previous process is still holding on to the serial port.
And yes, I've had my program find itself in a zombie state before, and then the customer complain that the program isn't working anymore because the program fails to attach to the serial port on restart. The result is either walking the user through killing a process in the task manager or having them reboot, neither of which is a particularly user-friendly task.
You write your custom code to free objects in your disposable class. It's for you to write code to free unmanaged and managed code.
This is why we need Dispose
function. All freeing up of memory is not automatic, like you said in unmanaged code's case.
Now, if you think that unmanaged code is automatically freed by OS, it's not true. There are many handles and locks that may remain active if not properly disposed by your application.
First, I would like to point out that IDisposable
is not only for Windows kernel objects or unmanaged memory. Rather, it is for things that the garbage collection does not understand (of which kernel objects are a special case).
A small example, just to give you an idea:
sealed class Logger : IDisposable
{
private bool _disposed = false;
public Logger()
{
System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STARTED" );
}
~Logger()
{
Dispose();
}
public void Dispose()
{
if ( !_disposed )
{
System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STOPPED" );
_disposed = true;
}
}
public void WriteMessage( string msg )
{
System.IO.File.AppendAllText( @"C:\mylog.txt", "MESSAGE: " + msg );
}
}
Note that this Logger
class does not "hold" any kernel-object-style resources. It opens file and closes it right away. However, there is this logic behind that it should write "LOG STOPPED" when the object is destroyed. This logic is something that GC cannot understand - and this is the place to use IDisposable
.
Therefore, you cannot count on Windows cleaning up kernel objects after you. There might still be something even Windows doesn't know.
That being said, however, provided your disposable objects are properly written (i.e. calling Dispose in finalizer), they will still be disposed by the CLR upon process exit.
So, the answer is: yes, it is not necessary to call Dispose
just before project exit. But NOT because disposable resources are kernel objects (because they're not necessarily are).
Edit
As Josh Einstein correctly pointed out, finalizers are actually not guaranteed to run on process exit. So then the answer becomes: always call Dispose, just to be sue
精彩评论