How to: Create and Terminate Threads (C# Programming Guide)
This example demonstrates how an auxiliary or worker thread can be
created and used to perform processing in parallel with
the primary thread. Making one thread wait for another and
gracefully terminating a thread are also demonstrated. For
background information on multi-threading, see
Managed Threading and
Using Threading (C# Programming Guide).
The example creates a class named Worker that
contains the method that the worker thread will execute
called DoWork. This is essentially the Main
function for the worker thread. The worker thread will
begin execution by calling this method, and terminate
automatically when this method returns. The DoWork
method looks like this:
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
The Worker class contains an additional method
that is used to indicate to DoWork that it should
return. This method is called RequestStop, and
looks like this:
public void RequestStop()
{
_shouldStop = true;
}
The RequestStop method merely assigns the
_shouldStop data member to true. Because this
data member is checked by the DoWork method, this
has the indirect effect of causing DoWork to
return, thereby terminating the worker thread. However, it
is important to note that DoWork and
RequestStop will be executed by different threads.
DoWork is executed by the worker thread, and
RequestStop is executed by the primary thread, so the
_shouldStop data member is declared volatile,
like this:
private volatile bool _shouldStop;
The volatile keyword alerts the compiler that
multiple threads will access the _shouldStop data
member, and therefore it should not make any optimization
assumptions about the state of this member. For more
information, see
volatile (C# Reference).
The use of volatile with the _shouldStop
data member allows us to safely access this member from
multiple threads without the use of formal thread
synchronization techniques, but only because
_shouldStop is a bool. This means that only
single, atomic operations are necessary to modify
_shouldStop. If, however, this data member were a
class, struct, or array, accessing it from multiple
threads would likely result in intermittent data
corruption. Consider a thread that changes the values in
an array. Windows regularly interrupts threads in order to
allow other threads to execute, so this thread could be
halted after assigning some array elements but before
assigning others. This means the array now has a state
that the programmer never intended, and another thread
reading this array may fail as a result.
Before actually creating the worker thread, the
Main function creates a Worker object and an
instance of
Thread. The thread object is configured to use the
Worker.DoWork method as an entry point by passing a
reference to this method to the Thread constructor,
like this:
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
At this point, although the worker thread object exists
and is configured, the actual worker thread has yet been
created. This does not happen until Main calls
the
Start method: