This topic was mostly covered in the volatile section earlier, but as this is a particularly common scenario (with a typical question being "How can I shut down a worker thread?" I think it's worth presenting a working pattern. Here's a code skeleton which just needs the work for the worker thread to perform to be filled in (and any member it needs, of course):
using System; using System.Threading; /// <summary> /// Skeleton for a worker thread. Another thread would typically set up /// an instance with some work to do, and invoke the Run method (eg with /// new Thread(new ThreadStart(job.Run)).Start()) /// </summary> public class Worker { /// <summary> /// Lock covering stopping and stopped /// </summary> readonly object stopLock = new object(); /// <summary> /// Whether or not the worker thread has been asked to stop /// </summary> bool stopping = false; /// <summary> /// Whether or not the worker thread has stopped /// </summary> bool stopped = false; /// <summary> /// Returns whether the worker thread has been asked to stop. /// This continues to return true even after the thread has stopped. /// </summary> public bool Stopping { get { lock (stopLock) { return stopping; } } } /// <summary> /// Returns whether the worker thread has stopped. /// </summary> public bool Stopped { get { lock (stopLock) { return stopped; } } } /// <summary> /// Tells the worker thread to stop, typically after completing its /// current work item. (The thread is *not* guaranteed to have stopped /// by the time this method returns.) /// </summary> public void Stop() { lock (stopLock) { stopping = true; } } /// <summary> /// Called by the worker thread to indicate when it has stopped. /// </summary> void SetStopped() { lock (stopLock) { stopped = true; } } /// <summary> /// Main work loop of the class. /// </summary> public void Run() { try { while (!Stopping) { // Insert work here. Make sure it doesn't tight loop! // (If work is arriving periodically, use a queue and Monitor.Wait, // changing the Stop method to pulse the monitor as well as setting // stopping.) // Note that you may also wish to break out *within* the loop // if work items can take a very long time but have points at which // it makes sense to check whether or not you've been asked to stop. // Do this with just: // if (Stopping) // { // return; // } // The finally block will make sure that the stopped flag is set. } } finally { SetStopped(); } } } |
Note the second part of the comment in the Run
method - often you will want to set up a
producer/consumer queue (as with the code given in the Monitor
methods section,
and that works fine with the pattern above so long as you make sure that when another thread tells
the worker thread to stop, it pulses the monitor on the queue to make sure that the worker thread
wakes up. (You can do this either by adding a "no-op" work item, or by modifying the
the class implementing the queue to add a mechanism just tell worker threads to wake up and return
a null work item.
In .NET v2, where properties can have different access for setters and getters, I'd recommend turning
the SetStopped
method into a setter for the Stopped
property. I wouldn't
recommend changing the Stop
method into a setter, however. This is partly because it needs
to be public, but should only go from false
to true
, and partly as a gut
instinct in terms of the word stop being more forceful as a noun than just setting a property to true
- it feels to me like properties shouldn't usually have as much of an effect on other threads as this
one does.
The above code is fine for occasional use, but including it in several classes introduces a fair amount of redundancy. It's not very much work to abstract most of the above into a separate class and provide more functionality at the same time. The resulting class is a bit too long to include in an article, but can be found as part of my Miscellaneous Utilities library.
Back to the main C# page.