Search code examples
c#.netwinformsdispose

Disposable objects lifetime when closing WinForm application


I have a windows forms application which connects to a database. All heavy duty SQL queries are implemented in background threads so that they do not block the main thread. Some of those queries require a database transaction. All Db objects are initialized with using keyword to ensure proper disposal:

using (conn = OpenConnection())
{
    using (tran = conn.BeginTransaction())
    {
        // Do stuff
    }
}

Now, some users are inpatient and they just close the application while the application has an active transaction in a background thread. Our DB admins discovered that those transactions are still active on the database for another few minutes or even longer which is a problem since open transaction locks data.

So, what happens with threads and disposable objects within those threads when user decides to close the application? Threads are created using Task.StartNew() methods.


Solution

  • Tasks

    Foreground and Background Threads:

    a background thread does not keep the managed execution environment running. Once all foreground threads have been stopped in a managed process (where the .exe file is a managed assembly), the system stops all background threads and shuts down.

    This also applies to threads from thread pool that are used by default when starting new task without specyfing other option:

    Task Schedulers

    The default task scheduler is based on the .NET Framework 4 ThreadPool

    Foreground and Background Threads:

    Threads that belong to the managed thread pool (that is, threads whose IsThreadPoolThread property is true) are background threads.

    Facts about application termination

    After that, I haven't found any further information what is going on out there after thread termination. What I did, is simple test instead:

    static void Main(string[] args)
    {
        Task.Factory.StartNew(Method);
        Task.Delay(1000);
    }
    
    static void Method()
    {
        try
        {
            while (true) { }
        }
        finally
        {
            File.WriteAllText(@"C:\Polygon\Test.txt", "test");
        }
    }
    

    The answer is, that finally block was not executed.

    Solutions

    ThreadAbortException - not here

    There are features in .NET like ThreadAbortException called as answer for thread termination, but:

    When the common language runtime (CLR) stops background threads after all foreground threads in a managed executable have ended, it does not use Thread.Abort.

    Process.Exited event - not here

    In case, when you run other process, you could use the following code:

    var myProcess = new Process();
    myProcess.EnableRaisingEvents = true;
    myProcess.Exited += new EventHandler(CleanUp);
    

    But this also doesn't work in case of current process.

    AppDomain.CurrentDomain.ProcessExit - works

    However the solution suggested by Avneesh works for current process well:

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.ProcessExit += CleanUp;
    
        Task.Factory.StartNew(Method);
        Task.Delay(1000);
    }
    
    static void Method()
    {
        try
        {
            while (true) { }
        }
        finally
        {
            CleanUp(null, EventArgs.Empty);
        }
    }
    
    static void CleanUp(object sender, EventArgs args)
    {
        File.WriteAllText(@"C:\Polygon\Test.txt", "test");
    }
    

    Process termination

    The answer for question what happens with rosources can be found in Terminating a Process article:

    • Any remaining threads in the process are marked for termination.
    • Any resources allocated by the process are freed.
    • All kernel objects are closed.
    • The process code is removed from memory.
    • The process exit code is set.
    • The process object is signaled.

    And How Processes are Terminated:

    • Any thread of the process calls the ExitProcess function. Note that some implementation of the C run-time library (CRT) call ExitProcess if the primary thread of the process returns.
    • The last thread of the process terminates.
    • Any thread calls the TerminateProcess function with a handle to the process.
    • For console processes, the default console control handler calls ExitProcess when the console receives a CTRL+C or CTRL+BREAK signal.
    • The user shuts down the system or logs off.

    DB Connection

    Connection is a resource so if processed is terminated, all its resources are freed. I checked how it looks like in practice.

    C# code:

    SqlConnection connection = new SqlConnection("Data Source=.;Initial Catalog=[database_name];Integrated Security=True");
    connection.Open();
    Debugger.Launch();
    connection.Close();
    connection.Dispose();
    

    T-SQL query for sessions lookup:

    SELECT COUNT(*) FROM sys.dm_exec_sessions
    WHERE nt_user_name = '[user_name]' AND program_name = '.Net SqlClient Data Provider'
    

    I've noticed that for each new connection, new session was created. The funniest thing was, that session was not freed after explicitely closing nor disposing the connection. But - what is currently in our field of interest - session was freed when application process was terminated.