17
votes

I have a simple try-catch-finally code block that works as expected in .NET3.5, but the same code behaves completely different on a project created with .NET4.5.1. Basically, in .NET4.5.1 the "finally" block doesn't get hit if an exception occurs which is not the behavior I expected from the try-catch-finally block. I tried in different machines, and had 2 other colleagues of mine also trying and we all got the same result. This is a concern for me, because I use the finally block to close DataReaders, certain connections, and whatnot.

.NET4.5.1 does not hit the "finally" block if an exception is thrown in RELEASE mode without debugger or when running the RELEASE compiled EXE file. In debug mode both .NET versions hit the "finally" block.

Again, the code below behaves as expected in .NET3.5 RELEASE mode without debugger but not in .NET4.5.1. Am I missing something? Can someone help?

class Program
{
    static void Main(string[] args)
    {
        try
        {
            string a = null;
            var x = a.Length;
            Console.WriteLine(x);
        }
        catch (Exception ex)
        {
            throw;
        }
        finally
        {
            Console.WriteLine("This is the finally block.");
        }
        Console.WriteLine("You should not be here if an exception occured!");
    }
}
3
@GrantWinney the OP is obviously trying to fix the underlying issue. A Console.WriteLine was added to use the variable that was not used in the "duplicate".Mike Zboray
Maybe edit the title of the question to be more descriptive (e.g. “Finally block not executed in .NET 4.5.1”)Arturo Torres Sánchez
@GrantWinney. He had two questions in that question. The first was resolved the second wasn't. He decided to create a separate question like he should of done at first. I think this question is valid if he edits this part from the original questionMark Hall
Thanks. My original post had 2 questions. Only one question got replied to. I thought it would be easier to go ahead give the credit to the best answer, edit the original post mentioning that the second question would be moved into a new thread. My mistake for asking 2 questions on the same thread.MarkJ
@MarkJ I did not see a difference between release/debug w/o debugger attached. The finally block always ran for me.Mike Zboray

3 Answers

19
votes

the code below behaves as expected in .NET3.5 RELEASE mode without debugger but not in .NET4.5.1. Am I missing something?

NOTE: I had overstated the level of undefined-ness of this behaviour; thanks to commenter Voo for pointing that out. I should have gone back to the spec in the first place.

Yes. The CLR is required by the CLI specification to end the program when there is an unhandled exception. It is only required to run finally blocks if the exception is handled. The spec is vague on the question of whether the CLR is required, permitted, or disallowed to execute finally blocks when there is an unhandled exception; the safe assumption is then to say that this is behaviour that is undefined by the specification, and that is up to a particular implementation.

The CLR can choose to run finally blocks for unhandled exceptions, or not, at its whim. Many people believe that the CLR uses this algorithm: upon exception, walk up the call stack, executing finally blocks as you go, looking for handlers; if no handler is found, terminate the process. The CLR is not required to conform to this algorithm in a program with an unhandled exception. In particular, the CLR is permitted to determine by black magic that there is no exception handler, and never run any finally blocks. Whether it chooses to do so or not in some versions of the CLR in some circumstances, I don't know. In no case can you rely on that behavior for the correctness of your program because a program that has an unhandled exception is not correct.

The specification also notes that the CLR can choose to offer to start debuggers or not, at its whim. The CLR is not required to do the same thing in debug or release, and it is not required to do the same thing from version to version.

The problem here is that you formed an expectation based on past experience, but there is no documentation which says that past experience is a basis for a prediction of the future. Rather, just the opposite; the CLR is permitted to change its behavior on the basis of the phase of the moon if it likes, in a program that has an unhandled exception.

If you want your program to behave predictably then do not throw unhandled exceptions.

So if I understand you correctly, as long as there is another catch somewhere upstream, the finally block will execute?

No, I didn't say that. Let's break it down.

If there is an uncaught exception in the program then the program's behavior is implementation-defined. Whatever behavior you get, that's the behavior you got, and the CLR is within its rights to produce that behavior. That includes both running finally blocks and not running finally blocks.

Suppose there is not an uncaught exception, and an exception is thrown, and there is a finally block along the way to the catch. Is it guaranteed that the finally block will execute? No. There are many things that could prevent that finally block from executing in a legal program. For example, another finally block or exception filter along the way could go into an infinite loop or fast fail, either of which would prevent the finally block from executing. If you ABSOLUTELY POSITIVELY must have some cleanup code run then you need to be researching Constrained Execution Regions. (I don't know how they work; I've never had need to learn. I hear they are tricky.).

What is guaranteed is that if control leaves a finally-protected block then the finally code will run. Code run during exception filters does not count as leaving the block, and failing fast does not cause program control to exit a block, it causes program control to end abruptly. Obviously infinite loops cause control to never exit a block.

I suppose in the case of a truly unhandled exception, the program should terminate anyways so an orphaned DB connection/transaction shouldn't be an issue?

Whether it is an issue or not, I cannot say. Ask the author of your database.

It is very likely that the program will terminate, though again I note that the CLR is not required to have that behavior. Suppose for example there is some thread that keeps on running while the CLR is trying to figure out whether you have a debugger installed or not. The CLR is within its rights to take arbitrarily long to figure that out, and therefore within its rights to keep that thread running. Whether it does or not, I don't know. What I do know is that I would not want to rely on either behavior.

Also, does using the 'AppDomain.CurrentDomain.UnhandledException event count as 'handling'

Nope. If that thing runs then there was an unhandled exception, and the behavior of the program is implementation-defined. That event handler should be used only to do things like log the fact that the program has a bug.

4
votes

On top of what Lipper wrote, note that it is written in MSDN... Under the try...finally:

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.

and

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important.

and then it goes on to explain that if you put a try... catch at a "high" level then the inner try... finally will be executed.

3
votes

Prior to Framework 4.0 unhandled exceptions launched 'Microsoft .NET Error Reporting Shim', which shows the dialog offering to 'Debug' or 'Close program'. The shim allows .NET applications to close "cleanly".

Starting with Framework 4.0 (as far as I can tell) unhandled exceptions result in Windows launching Windows Error Reporting (WER) which shows-up as Windows Problem Reporting in Task Manager. This application shows a similar dialog to the shim but takes a more hardline approach to killing the application, probably calling TerminateProcess or TerminateThread which would not allow any further code to execute in the misbehaving process.