Recently, I encountered an anomaly in the manner by which a worker thread was terminated. It prompted me to do some searching and I realized that I was constructing thread code in a haphazard manner. In hopes of beating this subject to death, I'd like some opinions, please.The issue is real and serious; the use of a Thread.Abort locked up the application and prevented further disposals and terminations of a very formidable (I won't say dangerous, but...) industrial device. Luckily programmable logic stepped in, but any safety systems anomaly is of great concern.Consider a simple thread example:using System;using System.Diagnostics;using System.Threading;namespace RapperThreads{    class Program    {        static void Main(string[] args)        {            Debug.Assert(!rapperEnd.WaitOne(0));            Console.Write(We choose);            rapperRefrain = new string[] {  go to the moon,,  and do the other things, };            for (int index = 0; index < rapperThread.Length; index++)            {                rapperThread[index] = new Thread(JFKRapper);                rapperThread[index].IsBackground = true;                rapperThread[index].Name = rapperRefrain[index];                rapperThread[index].Start((object)index);            }            for (int index = 0; index < 3; index++)            {                Console.Write( in this decade to);                Thread.Sleep(900);            }            rapperEnd.Set();            Thread.Sleep(0);            Console.WriteLine( not because they are easy, but because they are hard...);            lock (rapperEnd)                foreach (Thread thread in rapperThread)                    if (thread != null)                        if (thread.IsAlive)                        {                            try { thread.Abort(); }                            catch (Exception ex) { Console.WriteLine(ex.ToString()); }                            try { if (!thread.Join(100)) throw new Exception(); }                            catch (Exception ex) { Console.WriteLine(ex.ToString()); }                        }            rapperThread[0] = rapperThread[1] = null;   //just for orderliness            Console.ReadKey();        }        private static void JFKRapper(object indexObject)        {            int index = (int)indexObject;            try            {                while (!rapperEnd.WaitOne(0))                {                    // Create something (say, a TCP connection) that must be explicitly                    // closed/disposed.                    try                    {                        while (!rapperEnd.WaitOne(500 + 100 * index))                            // Do the expected work, over and over until some anomaly (exception or                            // termination) is detected.                            Console.WriteLine(rapperRefrain[index]);                    }                    catch (Exception ex)                    {                        // Report the exception anomaly if and only if termination has not been                        // requested.                        if (!rapperEnd.WaitOne(0))                            Console.WriteLine(ex.ToString());                    }                    finally { /* Tidy/dispose of created something. */ }                }            }            catch (Exception ex)            {                // Report the exception anomaly if and only if termination has not been requested.                if (!rapperEnd.WaitOne(0))                    Console.WriteLine(ex.ToString());            }            finally            {                // Tidy.                lock (rapperEnd)                    rapperThread[index] = null;            }        }        private static ManualResetEvent rapperEnd = new ManualResetEvent(false);        private static string[] rapperRefrain = null;        private static Thread[] rapperThread = new Thread[2];    }}Program.Main starts two threads and then enters a working loop of its own. The result is a mix of President Kennedy's Rice Moon speech and We chose to go to the moon) that looks like this:We choose in this decade to go to the moon,and do the other things,in this decade to go to the moon,and do the other things,go to the moon,in this decade to and do the other things,go to the moon,and do the other things,go to the moon,not because they are easy, but because they are hard...JFKRapper starts, makes something that must be explicitly and properly disposed of (well, the comments mark where this would be; imagine it's a TCP session), uses that something until told to stop (but does not use it too frantically), disposes of the something (TcpClients must be Closed as their finalization may occur only after an extended interval) and then ends. If, by some chance, a catastrophe occurs, JFKWrapper tidies the something, recreates it and continues performing good works until told to stop.This is all pretty much vanilla stuff. OK, here are the questions:See that Abort near the end of Program.Main? It's wrapped in a try/catch block not only to announce the inevitable exception but to report that the Abort had to be used. However, to our horror, we watched that call lock up the application because the (equivalent) to the rapperEnd.Set statement took too long to complete (many subscribers to an event chain). Any ideas as to what's going on? ...and, No, ensure that your thread ends benignly is not an acceptable answer: the Abort is the never-fail last resort.In most cases, is it even necessary to check IsAlive other than to ensure a benign environment for the Join?Is it a good or bad idea to set rapperThread[index] = null in the outer finally paragraph of the worker method?See the Thread.Sleep(0) just after rapperEnd.Set? Does that actually give the worker thread any cycles? It doesn't seem to.Note that, at the end of Program.Main, what might be Dispose code uses foreach (Thread thread in rapperThread) to produce a local copy of the thread reference. That's to stop the reference from being yanked out from under the feet of the method. Good idea or bad?Now for the lock... The event rapperEnd is used as a lock to make more orderly the tidying of the thread reference(s), to much the same purpose as the aforementioned use of a local copy of the thread reference. The competitors are the outer finally paragraph in JFKWrapper and the thread termination code at the end of Program.Main. Again, good or bad idea; I'd sure hate to see a deadly embrace at an inopportune time.Assuming that the lock is a good idea, must the lock object (in this case, the ManaualResetEvent) be disposed of explicitly (it isn't)?Suppose you were to replace the ManualResetEvent with a CancellationToken. Just what is it that CancellationTokens give you that ManualResetEvents don't?
Moonshot & `Thread.Abort` - dealing with the fallout
c#;multithreading;join;locking