A reusable Windows service template - part 3

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.