19

I have created the following C# program:

namespace dispose_test
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var disp = new MyDisposable())
            {
                throw new Exception("Boom");
            }
        }
    }

    public class MyDisposable : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("Disposed");
        }
    }
}

When I run this using dotnet run, I see the following behavior:

  • Windows: Exception text written to console, "Disposed" printed ~20 second later, program exits.
  • Linux: Exception text written to console, program exits immediately. "Disposed" never written.

The delay on Windows is annoying, but the fact that Dispose() isn't called at all on Linux is troubling. Is this expected behavior?

EDITS Clarifications/additions from the conversation below:

  • This is not specific to using/Dispose(), which is just a special case of try/finally. The behavior also occurs generally with try/finally - the finally block is not run. I have updated the title to reflect this.
  • I have also checked for the execution of Dispose() by writing a file to the filesystem, just to ensure that problem wasn't related to stdout being disconnected from the console before Dispose() is run in the case of an unhandled exception. Behavior was the same.
  • Dispose() does get called if the exception is caught anywhere within the application. It's when it goes completely unhandled by the application that this behavior occurs.
  • On Windows, the long gap is not due to compilation delay. I started timing when the exception text was written to the console.
  • My original experiment was doing dotnet run on both platforms, which means separate compilations, but I have also tried by doing dotnet publish on Windows and directly running the output on both platforms, with the same result. The only difference is that, when run directly on Linux, the text "Aborted (core dumped)" is written after the exception text.

Version details:

  • dotnet --version -> 1.0.4.
  • Compiling to netcoreapp1.1, running on .NET Core 1.1.
  • lsb-release -d -> Ubuntu 16.04.1 LTS
nlawalker
  • 6,364
  • 6
  • 29
  • 46
  • You should write out something to the console before the exception to see if the 20sec delay is a compilation delay or not. – xxbbcc May 17 '17 at 19:10
  • 20 seconds on Windows? Really? With my old-fashioned .NET 4.0, it's instant. That really makes me want to switch over to .NET core... – Joe Enos May 17 '17 at 19:10
  • @xxbbcc it's not compilation delay. The ~20 second gap is between the output of the exception text and the output of "Disposed". – nlawalker May 17 '17 at 19:11
  • @nlawalker Ah, ok. That's pretty concerning then. – xxbbcc May 17 '17 at 19:12
  • @JoeEnos Compiled to .NET 4.6 on VS 2017, and running the output directly from the command line (not from within Visual Studio), I also get a long gap, maybe 10 seconds or so. – nlawalker May 17 '17 at 19:14
  • 5
    Are you sure that `Dispose` is not being called? Could it be that it is called but `stdout` is already detached? You may try to change `Console.WriteLine` with `File.WriteAllText` and see if a file is written or not. – Federico Dipuma May 17 '17 at 19:15
  • 2
    @FedericoDipuma Yes, tried that as well, same result - the file gets written on Windows and does not on Linux. – nlawalker May 17 '17 at 19:16
  • Can you clarify exactly how you are compiling and running this code? Are you executing the exact same IL on both platforms, i.e. generated using the same compiler? That would help focus the question on whether the correct IL was generated for the `using` statement and the runtime is somehow killing the process before the `finally` clause gets to execute, or if the wrong IL was generated and just doesn't even have the `finally` clause in the first place. – Peter Duniho May 17 '17 at 19:28
  • @PeterDuniho I have tried both ways with the same results. At first I was using `dotnet run` on each platform separately, compiling separately on each platform. But I also tried doing `dotnet publish` on Windows, running the app directly there with `dotnet dispose_test.dll`, then moving the publish folder to Linux and running `dotnet dispose_test.dll` there. The only difference is that running the DLL directly on Linux adds "Aborted (core dumped)" to the end of the console output. – nlawalker May 17 '17 at 19:44
  • 2
    What happens if you write the `using` out explicitly, i.e. with `try/finally`? What happens if you add a `catch` so that the exception is in fact handled? – Peter Duniho May 17 '17 at 19:50
  • @PeterDuniho `try/finally` gives the same behavior. For `catch`, if I actually handle the exception and let execution exit the `catch` block without throwing, the `finally` will be called. If I just `throw` from within the `catch`, the `finally` does not get called. – nlawalker May 17 '17 at 20:07
  • 3
    Sounds like a bug that ought to be reported (e.g. through Connect or the GitHub issues). From your description, seems like an unhandled exception tears down the process without unwinding the stack, even when `finally` clauses are present. I presume that this really has less to do with `IDisposable` and more to do with the lack of execution of the `finally` clauses, whatever they might contain. – Peter Duniho May 17 '17 at 20:35
  • I have filed an issue here: https://github.com/dotnet/coreclr/issues/11695 – nlawalker May 17 '17 at 20:50
  • Related questions: [Exception handling (contradicting documentation / try-finally vs. using)](https://stackoverflow.com/questions/42250582/exception-handling-contradicting-documentation-try-finally-vs-using) and also [The finally block is not running on .NET 4.0, why?](https://stackoverflow.com/questions/4193493/the-finally-block-is-not-running-on-net-4-0-why) – Theodor Zoulias Mar 12 '23 at 11:11

2 Answers2

15

Official response is that this is an expected behavior.

Interestingly enough, the C# doc page on try-finally explicitly calls this out right at the top (emphasis mine)

Within a handled exception, the associated finally block is guaranteed to be run. However, if the exception is unhandled, execution of the finally block is dependent on how the exception unwind operation is triggered. That, in turn, is dependent on how your computer is set up. For more information, see Unhandled Exception Processing in the CLR.

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important. However, if you have statements in a finally block that must be run even in that situation, one solution is to add a catch block to the try-finally statement. Alternatively, you can catch the exception that might be thrown in the try block of a try-finally statement higher up the call stack. That is, you can catch the exception in the method that calls the method that contains the try-finally statement, or in the method that calls that method, or in any method in the call stack. If the exception is not caught, execution of the finally block depends on whether the operating system chooses to trigger an exception unwind operation.

One thing I found in my experimentation is that it doesn't appear to be enough to catch the exception, you have to handle it as well. If execution leaves the catch block via a throw, the finally will not run.

Community
  • 1
  • 1
nlawalker
  • 6,364
  • 6
  • 29
  • 46
2

If you surround this with a try-catch, the finally block will run when the exception is caught (handled) by the outer catch.

    static void Main(string[] args)
    {
        try
        {
            using (var disp = new MyDisposable())
            {
                throw new Exception("Boom");
            }
        }
        catch (Exception e)
        {
            throw;
        }
    }
Tom Deseyn
  • 1,735
  • 3
  • 17
  • 29