1

Is there an event that will tell me whether a currently running thread is being aborted mid-way not because the user called Abort but because the program is terminating?

I need this for every thread shutting down along with information about the thread Id.

I was hoping it will be the AppDomainUnload event but the documentation says nilch about it.

This page on the MSDN says:

When the runtime stops a background thread because the process is shutting down, no exception is thrown in the thread. However, when threads are stopped because the AppDomain.Unload method unloads the application domain, a ThreadAbortException is thrown in both foreground and background threads.

To test the fact, I wrote this little program that spawns two long-running threads, one of which is a foreground thread while the other a background thread, and immediately proceeds to exit the current process, thereby attempting to unload the current appdomain.

This should have called not only the DomainUnload handler but also attempted to abort the two running threads and sent them a ThreadAbortException.

But none of that happens.

The Output window displays the following output:

[#10] [Foreground]Starting to do stuff.
The thread 0x183c has exited with code 0 (0x0).
[#9] [Worker]Starting to do stuff.
[#10] [Foreground]Slept for 5 seconds. Now finishing up the doing of stuff.
The thread 0x2954 has exited with code 0 (0x0).
The program '[11224] KnowAboutDyingThread.vshost.exe' has exited with code 0 (0x0).

Here is my code:

using System;
using System.Diagnostics;
using System.Threading;

namespace KnowAboutDyingThread
{
    // Source: https://msdn.microsoft.com/en-us/library/h339syd0%28v=vs.110%29.aspx
    // When the runtime stops a background thread because the process is shutting down, 
    // no exception is thrown in the thread. However, when threads are stopped because the 
    // AppDomain.Unload method unloads the application domain, a ThreadAbortException is 
    // thrown in both foreground and background threads.

    // I am trying that out
    // I asked a question here about it: http://stackoverflow.com/questions/37552668/is-there-a-way-to-know-if-a-thread-is-being-aborted-because-the-program-is-termi
    // Permalink: http://stackoverflow.com/q/37552668/303685

    class Program
    {
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
            ThreadPool.QueueUserWorkItem(Do);
            var t = new Thread(Do);
            t.Name = "Foreground";
            t.Start();

            Process.GetCurrentProcess().Close();
        }

        private static void CurrentDomain_DomainUnload(object sender, EventArgs e)
        {
            Debug.Print("Unloading current appdomain...");
        }

        static void Do(object state)
        {
            try
            {
                Debug.Print(string.Format($"[#{Thread.CurrentThread.ManagedThreadId}] [{Thread.CurrentThread.Name ?? "Worker"}]Starting to do stuff."));
                Thread.Sleep(5000);
                Debug.Print(string.Format($"[#{Thread.CurrentThread.ManagedThreadId}] [{Thread.CurrentThread.Name ?? "Worker"}]Slept for 5 seconds. Now finishing up the doing of stuff."));
            }
            catch(ThreadAbortException abort)
            {
                Debug.Print(string.Format($"[#{Thread.CurrentThread.ManagedThreadId}] [{Thread.CurrentThread.Name ?? "Worker"}]Do got ThreadAbortException: {abort.GetType().Name}: {abort.Message}"));
            }
            catch(Exception ex)
            {
                Debug.Print(string.Format($"[#{Thread.CurrentThread.ManagedThreadId}] [{Thread.CurrentThread.Name ?? "Worker"}]Do had an exception: {ex.GetType().Name}: {ex.Message}"));
            }
        }
    }
}





  [1]: https://msdn.microsoft.com/en-us/library/system.appdomain.domainunload%28v=vs.110%29.aspx
Water Cooler v2
  • 32,724
  • 54
  • 166
  • 336
  • Don't `Thread.Abort`. Whatever you're using it for, you're taking the wrong approach. `Thread.Abort` is evil. You should be asking "how do I terminate my program without calling `Thread.Abort`". Once again, [`Thread.Abort` is evil.](http://stackoverflow.com/questions/3632149/question-about-terminating-a-thread-cleanly-in-net) – spender May 31 '16 at 18:12
  • I am well aware of the ills of using `Thread.Abort` and of `Thread.Interrupt` and my question has little to do with the side-effects of using those methods. – Water Cooler v2 May 31 '16 at 18:13
  • 1
    I think you need to do `Process.GetCurrentProcess().Kill();` to get the behavior you want. doing `Close()` triggers a graceful shutdown, but the `Do` on the forground thread does not have any way to monitor for the close message so will not trigger the process. And even then, `DomainUnload` still might not fire due to it being forcefully terminated, but at least you might not get the "Now finishing...." section. – Scott Chamberlain May 31 '16 at 19:08
  • @ScottChamberlain Thank you. I tried calling `Kill` but that doesn't help either. It just terminates abruptly without printing anything at all. I did a `Thread.Sleep(1500)` on the `Main` thread but that only allows for the 2 spawned threads to begin. Importantly, none of the clean-up code is executed. – Water Cooler v2 May 31 '16 at 19:25
  • To communicate with the thread you need to use a stream (some times called a pipe) or a semaphore (like WaitOne). Using Standard Input/Output like Scott recommended is one type of stream, but there are other way of accomplishing the same. You may want to read following article from msdn : https://msdn.microsoft.com/en-us/library/windows/desktop/ms686358(v=vs.85).aspx – jdweng May 31 '16 at 19:36
  • @jdweng Thank you. I will go through the documentation for unmanaged threading once I have gone through the managed one. I am doing a second round of reading of Managed Threading, this time summarizing my thoughts, taking notes and trying out code samples. Once done, I will go through that link you have shared. Many thanks. – Water Cooler v2 May 31 '16 at 20:11
  • I try to avoid timers and do everything using Events similar to how a Form project works. A form project uses button clicks and and mouse clicks. A Background worker class stays open so you can call do work as often as you like. I also like passing a STATE object to the BackGroundWorker like a parameter list. See following example : https://msdn.microsoft.com/en-us/library/mt679044.aspx – jdweng May 31 '16 at 20:36
  • 1
    As asked, this question is fairly broad. It's not really clear what answer would address your specific concern. As the documentation explains, `DomainUnload` is not raised in the default domain, so the code example you posted isn't relevant at all. It's by design that the event isn't raised in that example. _Maybe_ what you want is to execute code in a different domain, where the event would be raised; but you might have to unload the domain explicitly to get the event to be raised. Ultimately, if Windows kills a process, the threads in the process aren't going to know about it. They just die. – Peter Duniho Jun 01 '16 at 02:17
  • @PeterDuniho Thank you for your remarks. I found them useful. I will get at this after a few hours of sleep and post back an update. – Water Cooler v2 Jun 01 '16 at 02:23

1 Answers1

0

Background threads are not supposed to be gracefully terminated.

You will not be able to catch any async exceptions. This holds for foreground threads too.

In a foreground thread, you will be able to gracefully get into a finally block, but not the catch blocks.

The simplest thing you can do is probably using finalized objects:

using System;
using System.Diagnostics;
using System.Threading;

namespace ReliableStop
{
    class Program
    {
        static void Main(string[] args)
        {
            Debug.WriteLine("-- Started Main. Thread: {0}", Thread.CurrentThread.ManagedThreadId);

            var everStarted = new ManualResetEvent(false);

            var t = new Thread(o =>
            {
                Debug.WriteLine("-- Thread entered here.");
                everStarted.Set();

                using (new PostMortemThreadDump())
                {
                    try
                    {
                        Thread.Sleep(100);
                    }
                    catch
                    {
                        Debug.WriteLine("-- Attempt to catch everything.");
                    }
                    finally
                    {
                        Debug.WriteLine("-- Attempt to process finally.");
                    }                    
                }
            });
            t.IsBackground = true;
            t.Start();

            // Check that the thread has started.
            everStarted.WaitOne();

            // ... Good bye, no need to kill, I will die on my own.
        }
    }

    class PostMortemThreadDump : IDisposable
    {
        int threadId { get; }
        public PostMortemThreadDump()
        {
            threadId = Thread.CurrentThread.ManagedThreadId;
        }

        ~PostMortemThreadDump()
        {
            Dispose(false);
        }

        void Dispose(bool disposing)
        {
            if (disposing)
            {
                Debug.WriteLine("-- PostMortemThreadDump. Finished normally.");
            }
            else
            {
                Debug.WriteLine("-- PostMortemThreadDump. Thread: {0}", threadId);
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Output:

-- Started Main. Thread: 9
-- Thread entered here.
The thread 0x6fc8 has exited with code 0 (0x0).
The thread 0x12b4 has exited with code 0 (0x0).
-- PostMortemThreadDump. Thread: 10
The program '[25260] ReliableStop.vshost.exe' has exited with code 0 (0x0).

Now, if you change to using a foreground thread you also get the `finally block executed:

-- Started Main. Thread: 9
-- Thread entered here.
The thread 0x4d54 has exited with code 0 (0x0).
The thread 0x20c8 has exited with code 0 (0x0).
-- Attempt to process finally.
-- PostMortemThreadDump. Finished normally.
The thread 0x6928 has exited with code 0 (0x0).
The program '[26328] ReliableStop.vshost.exe' has exited with code 0 (0x0).

As you can see, the finalizer runs in both cases, and in a foreground thread, you get the finally blocks executed.

Calling Process.GetCurrentProcess().Close() does not affect this scenario.

George Polevoy
  • 7,450
  • 3
  • 36
  • 61