可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What happens if the program exits unexpectedly (either by exception or the process is terminated)? Are there any situations like this (or otherwise) where the program will terminate, but IDisposable
objects won't be properly disposed of?
The reason I'm asking is because I'm writing code that will communicate with a peripheral, and I want to make sure there's no chance it will be left in a bad state.
回答1:
If the cause is an exception and thrown from within a using
block or a try catch finally
block, it will be disposed as it should. If it is not catched by a using
block it is not disposed automatically (like it doesn't do when the application closes properly).
A sample:
IDisposable d1 = new X();
using (IDisposable d2 = new X())
{
throw new NotImplementedException();
}
d1.Dispose();
d1
is not disposed, d2
usually is. Some types of exceptions may prevent handling of using
blocks and also some program crashes. If the cause is a power failure or system crash, there is nothing you can do either of course.
回答2:
If the program quits unexpectedly (for example you kill the process) there are absolutely no guarantees that the IDisposable.Dispose
method will be called. You'd better not rely on it for such events. The Dispose method must be called manually by your code, it's not something that the CLR will call automatically for you.
回答3:
In addition to Patrick Hofman's and Alexei's answer cleanup may be not performed even if the application terminates correctly.
As you probably know the Dispose
method is not called when the garbage collector collects the object which implements IDisposable
interface. But the GC will call the Finalize
method also known as finalizer. In it you should write your cleanup logic using Dispose Pattern. And yes, the .Net Framework will try to run all finalizers, but there is no guaranty that they ever be executed.
As an example, the program bellow has the very long running finalizer. Therefore, the .Net will terminate the process and you will never see the message.
class FinalizableObject
{
~FinalizableObject()
{
Thread.Sleep(50000);
Console.WriteLine("Finalized");
}
}
class Program
{
static void Main(string[] args)
{
new FinalizableObject();
}
}
This can be caused by any long running operation like releasing a network handle or something else which will require much time.
Therefore, you should never rely on finalizers and disposable objects. But all opened handles to kernel objects will be closed automatically, so you should not worry about them.
I will recommend you to read few interesting articles about finalizers and the GC in addition to the answers:
- Everybody thinks about garbage collection the wrong way (Raymond Chen)
- When everything you know is wrong, part one (Eric Lippert)
- When everything you know is wrong, part two (Eric Lippert)
- Terminating a Process (MSDN)
回答4:
A very simple test using a console application illustrate that Dispose is not called on process kill:
class DisposableTest : IDisposable
{
public void Dispose()
{
Console.WriteLine("Dispose called");
}
}
...
using (DisposableTest sw = new DisposableTest())
{
Thread.Sleep(20000);
}
Killing the process with Task Manager would not trigger Disposable.Dispose()
method. Waiting for 20 seconds will.
So, as already mentioned, do not rely on objects disposable when application crashes or gets killed. However, exceptions should trigger it. I am just wondering if exception such as StackOverflowException
or OutOfMemoryException
will always trigger Dispose().
[edit]
Just tested my curiosities:
StackOverflowException
gets the process terminated, so no Dispose() is called
OutOfMemoryException
allows normal call of Dispose()
回答5:
Yes, there are such situations. For example, calling TerminateProcess
, calling Environment.FailFast
, or encountering an internal CLR error will all cause the process to exit without running any additional code. In such situations, the best thing you can do is say "oh well".
Even if the process doesn't exit unexpectedly, calling Dispose
is a manual action. It's not something done through the runtime, except when an object implementing a finalizer that calls Dispose
is garbage collected. Therefore, forgetting to wrap a disposable in a using
or causing a memory leak that keeps the object alive is another way Dispose
may never be called.
The only reliable cleanup is performed by the operating system when a process exits -- all open handles to system objects are closed. When the last handle is closed, whatever cleanup implemented in the OS or a driver happens. If this cleanup code is not part of a driver but is supposed to be called by a user process, all you can do is make your code as robust as possible, or implement a watchdog process that handles cleanup for you.
回答6:
IDisposable is just an interface. There is absolutely nothing special about the way they are handled. When you call Dispose on an IDisposable (explicitly or via a using block), it calls the contents of your Dispose method. It gets garbage collected like any other object.
The purpose of the interface is to allow an implementer to define the cleanup of a type that may have managed or unmanaged resources that need to be explicitly cleaned up.
If these resources are all managed, garbage collection may be enough and implementations may just be for optimization.
If they are unmanaged or have some connection to unmanaged resources, garbage collection is probably not enough. This is why the full recommended implementation of IDisposable involves handling both explicit disposal and disposal by the runtime (via a finalizer).
Process shutdowns will not call Dispose and finalizers are not guaranteed to run...so you have to hope that destroying the process is sufficient by itself.