Browse DevX
Sign up for e-mail newsletters from DevX


Working with .NET Threads : Page 8

This article describes the dos and don'ts of the Thread class, and presents a wrapper class that simplifies starting a thread, correctly terminates a thread, and offers a more consistent class interface than that of the raw Thread class.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Killing Threads
One of the most common challenges developers face is the task of killing their worker threads, usually upon application shutdown. As mentioned previously, you should avoid calling Thread.Abort() to terminate your thread. Instead, in each iteration of the thread method, you should check a flag signaling whether to do another iteration or return from the method. As shown in Listing 2, the thread method Run() traces to the Output window the value of a counter in a loop:

int i = 0; while(EndLoop == false) { Trace.WriteLine("Thread is alive, Counter is " + i); i++; }

Before every loop iteration, Run() checks the Boolean property EndLoop. If EndLoop is set to false, Run() performs another iteration. WorkerThread provides the Kill() method, which sets the EndLoop to true, causing Run() to return and the thread to terminate. EndLoop actually gets and sets the value of the Boolean m_EndLoop member variable. Because Kill() will be called on a client thread, you must provide for thread-safe access to m_EndLoop. You can use any of the manual locks: you can lock the whole WorkerThread object using a Monitor, you can use ReaderWriterLock() except that ReaderWriterLock() is excessive for a property that will only be written once. I chose to use a Mutex:

bool EndLoop { set { m_EndLoopMutex.WaitOne(); m_EndLoop = value; m_EndLoopMutex.ReleaseMutex(); } get { bool result = false; m_EndLoopMutex.WaitOne(); result = m_EndLoop; m_EndLoopMutex.ReleaseMutex(); return result; } }

Kill() should return only when the worker thread is dead. To that end, Kill() calls Join().

However, because Kill() is called on the client thread, the WorkerThread object must store as a member variable a Thread object referring to the worker thread. Fortunately, there is already such a member—the m_ThreadObj member variable. You can only store the thread value in the thread method, not in the constructor, which executes on the creating client's thread. This is exactly what Run() does in this line:

m_ThreadObj = Thread.CurrentThread;

Note that calling Kill() multiple times is harmless. Also note that Kill() does the cleanup of closing the mutex. Finally, what if the client never calls Kill()? To answer that, the WorkerThread class implements IDisposable and a destructor, both calling Kill():

public void ~WorkerThread() { Kill(); } public void Dispose() { Kill(); }

It is important to understand that Kill() is not the same as Dispose(). Kill() is handling execution flow such as application shutdown or timely termination of threads, whereas Dispose() caters to memory and resource management, and disposing of other resources the WorkerThread class might hold. The only reason Dispose() is calling Kill() is as a contingency, in case the client developer forgets to do it.

Juval Löwy is a software architect and the principal of IDesign, a consulting and training company focused on .NET design and .NET migration. His latest book is Programming .NET Components (O'Reilly, 2003). Juval is Microsoft's Regional Director for the Silicon Valley, helping the industry adopt .NET. Juval is a frequent speaker at the major international software development conferences. Contact him at www.idesign.net.
Thanks for your registration, follow us on our social networks to keep up-to-date