Edit: This open-source template has now been updated and placed in a GitHub repository.
A few years ago I wrote a bunch of C# Windows services that were all based around the same basic template. Recently I decided to extract what I learned from that experience to make a better template.
This Windows service template is designed to:
- Provide all of the service infrastructure, so that the developer can focus primarily on the real work that the service does.
- Create a service infrastructure that starts/restarts, monitors, and logs the worker thread where the service's work is occurring.
- Isolate the service's work from the controlling infrastructure so that any crash is logged properly and doesn't bring down the service.
- Have relatively simple and small code, so that it's easy to understand, maintain, and debug.
- Enable the developer to test and debug the service within Visual Studio.
- Allow the service to install and uninstall itself from the command-line, without the use of InstallUtil.
- Remove any use of Thread.Sleep.
- Remove any cross-thread interactions that involve polling or "busy" loops.
- Reduce application-level cross-thread interactions that involve shared memory.
- Log everything that the service is doing, and especially cross-thread interactions.
This is the second entry in a 3-part series:
- Part 1 discusses the purpose of the template, along with its start modes - install, uninstall, and debugging.
- This part describes the core of the service template - a detailed design analysis of the controller and worker threads.
- Part 3 investigates the thread-safe logging class.
At its core, the service template has a simple model: a controller thread that controls and monitors a worker thread, which is where the real work of the service should be added.
Controller thread logic
The code below shows the controller thread's logic:
- The controller thread first initialises and starts the worker thread
- It then blocks and waits for the worker thread to crash or be stopped by the service control manager (SCM)
- It pauses after a worker thread crash, and then restarts the worker thread
- Otherwise it exits after the worker thread finishes, thereby stopping the service.
_____________________________________________________________________________________________________________________________________
// Invoked when the controller thread starts. private void ControllerThreadRunning() { this.AppLog.Information("Controller thread has started.", "ServiceMain.ControllerThreadRunning"); // Configure the worker thread. this.ThreadWorker = new BackgroundWorker(); this.ThreadWorker.WorkerReportsProgress = true; this.ThreadWorker.WorkerSupportsCancellation = true; this.ThreadWorker.DoWork += new DoWorkEventHandler(WorkerThreadRunning); this.ThreadWorker.ProgressChanged += new ProgressChangedEventHandler(WorkerThreadNotification); this.ThreadWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerThreadFinished); // And we're on our way. while ( !this.ServiceStopRequested ) { // Start worker thread and then block until it stops. this.ThreadWorker.RunWorkerAsync(); this.BlockControllerThread("Block controller thread until worker thread stops."); // If service stop not requested, the assumption is that // the worker thread crashed and needs to be restarted. if ( !this.ServiceStopRequested ) { this.PauseControllerThread("Pause before restarting worker thread.", this.RestartDelay); } } // This service will now stop. this.AppLog.Information("Service stopping at SCM request.", "ServiceMain.ControllerThreadRunning"); this.Cleanup(); }
_____________________________________________________________________________________________________________________________________
There's nothing very fancy about the code above. The worker thread consists of a BackgroundWorker that does an under-the-hood transition to the controller thread to show progress, and does a similar transition if the worker thread finishes or crashes. It's useful that these cross-thread transitions are managed within the .NET Framework rather than at the application level. This is because we casn assume that the framework-level code is more reliable than our application-level code.
The controller thread waits until either the worker thread crashes or the SCM issues a request to stop the service. In either case the controller is unblocked, and then decides whether to restart the worker thread or stop the service. To make this decision, it reads a flag (ServiceStopRequested) that's shared between threads (specifically an SCM thread and the controller thread) at the application level. The code fragment below shows how the SCM sets this flag.
_____________________________________________________________________________________________________________________________________
// SCM requests service stop using its own thread.
protected override void OnStop()
{
this.AppLog.Information("SCM requested service stop.", "ServiceMain.OnStop");
this.PrepareServiceStop();
base.OnStop();
}
// SCM requests service stop (via machine shutdown) using its own thread.
protected override void OnShutdown()
{
this.AppLog.Information("SCM requested service stop due to machine shutdown.", "ServiceMain.OnShutdown");
this.PrepareServiceStop();
base.OnShutdown();
}
// Setup conditions for controller thread to stop service.
private void PrepareServiceStop()
{
this.ServiceStopRequested = true;
if (this.ThreadWorker.IsBusy && !this.ThreadWorker.CancellationPending)
{
this.ThreadWorker.CancelAsync();
}
}
_____________________________________________________________________________________________________________________________________
The ServiceStopRequested property is protected by a lock for thread safety. Using a property with a lock is easier for a maintenance developer to understand and model than using the more fancy concept of a volatile field.
_____________________________________________________________________________________________________________________________________
// Set by an SCM thread when it wants this service // to stop, and read by the controller thread. // Protected by a lock, which is simpler to understand // than using the more fancy concept of a volatile field. // http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword private bool ServiceStopRequested { get { lock (LockFlag) { return m_ServiceStopRequested; } } set { lock (LockFlag) { m_ServiceStopRequested = value; } } }
_____________________________________________________________________________________________________________________________________
Worker thread logic
The screenshot below shows the code implementing the worker thread's logic:
- The worker thread repeats a cycle of work - this is where the code implementing the real work of the service should be placed
- Before the start of each work cycle it checks whether the controller thread has issued a cancellation request
- If so, it simply stops
- If the worker thread crashes, it has the same effect as a cancellation request.
_____________________________________________________________________________________________________________________________________
// This method runs on the worker (background) thread. // The work continues in cycles until it's asked to stop. // If the worker thread crashes, the controller // thread will restart it after an appropriate delay. private void WorkerThreadRunning(object sender, DoWorkEventArgs e) { Thread.CurrentThread.Name = THREAD_NAME_WORKER; this.AppLog.Information("Worker thread has started.", "ServiceMain.WorkerThreadRunning"); while ( !this.ThreadWorker.CancellationPending ) { this.SimulatedWorkCycle(); } e.Cancel = true; }
_____________________________________________________________________________________________________________________________________
Note that the real work that you want the service to do should be placed in the SimulatedWorkCycle() procedure - and of course that procedure should be renamed!
When the worker thread ends, either because of a "stop service" request from the SCM or because it crashed, the following callback code will run. This logs any unhandled exception and unblocks the controller thread.
_____________________________________________________________________________________________________________________________________
// Event callback when the worker thread finishes, crashes, or is cancelled. // This event method runs on an arbitrary thread from the threadpool, because // because that's what the SynchronizationContext.Default provider arranges. // https://msdn.microsoft.com/en-us/magazine/gg598924.aspx
private void WorkerThreadFinished(object sender, RunWorkerCompletedEventArgs e) { try { this.AppLog.Information("Worker thread has finished.", "ServiceMain.WorkerThreadFinished"); // Log any unhandled exception thrown by the worker thread. if (e.Error != null) { this.AppLog.Error(string.Concat("Worker thread exception", Environment.NewLine, e.Error.GetType().FullName, Environment.NewLine, e.Error.Message, Environment.NewLine, e.Error.StackTrace)); } } finally { // Everything related to the worker thread is done, so let's // unblock the controller thread to decide what to do next. this.UnblockControllerThread("Unblocking controller thread as worker thread has finished."); } }
_____________________________________________________________________________________________________________________________________
Controller thread blocking and unblocking
In the older and more primitive version of this template I used a "busy" loop in the controller thread to poll for an SCM request to stop the service or wait for a worker thread crash. And then another "busy" loop to wait for the worker thread cancellation request to take effect. Both loops used Thread.Sleep, which is rather ugly.
In this latest version of the template, I've switched over to using ManualResetEventSlim for blocking the controller thread and then un-blocking it after ther worker thread has crashed or finished. The ManualResetEventSlim object used by the controller thread waits indefinitely until it receives a signal from another thread. This is thread-safe, cleaner, and more elegant than the "busy" loops. Use of this object is encapsulated in the following methods.
_____________________________________________________________________________________________________________________________________
// Block and wait for signal from another thread. private void BlockControllerThread(string message) { this.AppLog.Information(message, "ServiceMain.BlockControllerThread"); ReleaseControllerThread.Reset(); ReleaseControllerThread.Wait(); } // Called by another thread to release the controller thread. private void UnblockControllerThread(string message) { this.AppLog.Information(message, "ServiceMain.UnblockControllerThread"); ReleaseControllerThread.Set(); } // Pause for the specified number of milliseconds. private void PauseControllerThread(string message, Int32 waitMilliseconds) { this.AppLog.Information(message, "ServiceMain.PauseControllerThread"); ReleaseControllerThread.Reset(); ReleaseControllerThread.Wait(waitMilliseconds); }
_____________________________________________________________________________________________________________________________________
In the final entry of this series, we'll look at the event logging class.