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 third entry in a 3-part series:
- Part 1 discusses the purpose of the template, along with its start modes - install, uninstall, and debugging.
- Part 2 describes the core of the service template - a detailed design analysis of the controller and worker threads.
- This part investigates the thread-safe logging class.
The screenshot below shows the Windows event log for the service template start and stop events.
Unfortunately the .NET Windows event log object only allows the date/time to be specified at the granularity of 1 second. If you create multiple events within a single second, you can't by default sort those events in the order in which they occurred. The trick I use to overcome this is to increment the Event ID for all events logged within a single second. As you can see above, sorting the display by both date/time and Event ID shows the events in the order that they actually occurred.
The code below shows the core of the class that logs these events.
_____________________________________________________________________________________________________________________________________
// Write message to event log. private void LogMessage(string message, EventLogEntryType messageType, string methodName) { // Extract name of thread that triggered this message. string threadName = Thread.CurrentThread.Name; if (threadName == null) { threadName = THREAD_NAME_SCM; } // Maintenance developer might want to know this fact. if (Thread.CurrentThread.IsBackground) { threadName = threadName + " (background)"; } // Setup full message and then write to event log.
string fullMessage = string.Concat("Thread: ", threadName, Environment.NewLine,
"Method: ", methodName, Environment.NewLine,
"Message: ", message);
// Note the assumption that no external call inside this protected // block will lock something in such a way as to cause a deadlock. lock (LockEventLog) { // Because EventLog's timestamp only has a granularity of 1 second, // we populate the EventId field with an incrementing integer wherever // an event occurs in the same second as the previous event. // The combination of Timestamp and EventId makes it easier to view // the real order of events in the Windows event log. if (Math.Abs((DateTime.Now - this.LastEventWriteTime).TotalSeconds) < 1) { this.EventOrder++; } else { this.EventOrder = 1; } this.LastEventWriteTime = DateTime.Now; this.AppEventLog.WriteEntry(fullMessage, messageType, this.EventOrder); } }
_____________________________________________________________________________________________________________________________________
This concludes our dive into latest version of the reusable Windows service template. The previous version of this template has proven to be very reliable in a demanding production environment, and this version should be even more robust.
One final caveat: be careful not to make any network call in the template infrastructure code. Network calls to other resources such as a database are notoriously less reliable than local calls.