Do IDisposable objects get disposed of if the prog

2020-04-01 08:13发布

问题:

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:

  1. Everybody thinks about garbage collection the wrong way (Raymond Chen)
  2. When everything you know is wrong, part one (Eric Lippert)
  3. When everything you know is wrong, part two (Eric Lippert)
  4. 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.