dotnet / docs

This repository contains .NET Documentation.
https://learn.microsoft.com/dotnet
Creative Commons Attribution 4.0 International
4.29k stars 5.92k forks source link

Exception handling default for top-level statements #25607

Open zahirtezcan opened 3 years ago

zahirtezcan commented 3 years ago

With .NET Core and also .NET 5 runtime, the default for stack-rewind on the main thread is, when the exception occurs the runtime does not rewind and your application crashes. Only thing you may get is an event trigger for uncaught exceptions from AppDomain.

What is the default exception handling for Top-level statements? Does it inject a try-catch block, so the stack unwinding occurs and we get our finally blocks working? Or, does it ignore everything about exception handling?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.


Associated WorkItem - 345704

svick commented 3 years ago

Top-level statements don't do anything special regarding exceptions. All they do is wrapping your code inside a class inside a method that is marked as the main method of the program.

Though I don't know anything about the behavior you're describing. As far as I can tell, finally blocks work just fine on the main thread in .Net Core.

zahirtezcan-bugs commented 3 years ago

finally blocks run if stack-unwinding occurs. And, for runtime it is optional to implement stack unwinding in absence of root catch statement.

Here is my quick tests on different online compilers, one of them is .NET 5 other is dotnet v2:

https://dotnetfiddle.net/PQqZO6 https://replit.com/@zahirtezcanbugs/NoCatchFinally

EDIT: It can be beneficient to provide code here as well

using System;

public class Program
{
    public static void Main()
    {
        try
        {
            AppDomain.CurrentDomain.UnhandledException += (sender, e) => Console.WriteLine("UNCAUGHT " + (e.IsTerminating ? "TERMINATING" : ""));
            throw new Exception("GO");
        }
        //uncomment following line for finally to work
        //catch{}
        finally
        {
            Console.WriteLine("FINALLY");
        }
    }
}
svick commented 3 years ago

Interesting, though I only see this behavior on Linux, not on Windows. And yes, it's the same with top-level statements.

This code:

using System;

try
{
    AppDomain.CurrentDomain.UnhandledException += (sender, e) => Console.WriteLine("UNCAUGHT " + (e.IsTerminating ? "TERMINATING" : ""));
    throw new Exception("GO");
}
finally
{
    Console.WriteLine("FINALLY");
}

Produces:

> dotnet run
UNCAUGHT TERMINATING
Unhandled exception. System.Exception: GO
   at <Program>$.<Main>$(String[] args) in C:\code\tmp\hwapp\Program.cs:line 6
FINALLY
> wsl dotnet run
UNCAUGHT TERMINATING
Unhandled exception. System.Exception: GO
   at $Program.$Main(String[] args) in /mnt/c/code/tmp/hwapp/Program.cs:line 6
>
zahirtezcan-bugs commented 3 years ago

This behaviour is default for the C++ compilers that I know of (MSVC, GCC, clang) unless you specify as a compiler flag. That is, for any thread I tend to put a root try-catch block to make RAII to work efficiently. Similarly, since unwinding and finally does not work, using statements do not work either.

So, interprocess communication or stateful service communication (some OS resources?) may not be shutdown gracefully and the documentation should reflect this accordingly.

BillWagner commented 3 years ago

Thanks for this discussion. As @svick points out, top level statements don't change the already defined behavior. That's worth stating in this article.