Windows Services Made Simpler, Part 2

Published on: Author: Michael 1 Comment

In the last article I provided an overview of Windows services and the issues involved in writing one compared to a normal program. In this article I will demonstrate how to write a simple service in a way that helps alleviate these issues. Before getting into the code for a service it is important to reiterate that a single process can host multiple services at the same time and each service can be started or stopped on its own. It is therefore useful to break up a service into its components so that each component does only what it is expected to do. When creating a service I find that there are 3 different components: service host, service instance(s), and service worker.


Service Host


The service host is the process that runs when the service starts. Its sole purpose is to house the service instances and provide discovery support for the SCM. The host may be responsible for process-wide functionality such as providing configuration information, error logging, IoC configuration, etc. but is otherwise generic and reusable across all services. It is also replaceable. What I mean by that is if, some day, we decide we no longer need the work in a service the host can go away and we lose no real functionality outside of infrastructure. Beyond discovery of instances the host does no real work. Since the host is replaceable we can use different implementations of the host depending upon whether we’re debugging the code or running as a normal service.


Service Instance


The service instance (referred to as the service hereafter) represents the individually runnable services that the SCM sees. Often we will have 1 service but we could easily support multiple within the same host. Similar to the host, the service is really just about lifetime management. It is more closely related to a regular program than the host. It is responsible for ensuring that the work continues until told (by the SCM) to stop.


Often, when creating a service, we start out with a regular console application that does some work each time it is called. If we want to repeat this work periodically then we may move it to a scheduled task. With a service it is necessary for the service to run until told to stop (services rarely stop themselves). The service is responsible for repeating the work until it is told to stop. As we will see later the service generally just moves the real work to a worker thread and then wraps it in an infinite loop of some sort to keep it running until told to terminate. The service should contain only the logic needed to keep the work repeating until told to terminate. This generally involves some sort of synchronization object and threading.


Service Worker


The worker is the component that does all the real, business work. It has no knowledge of services or, generally, threading. It just does something (whatever that means for the service). The worker may support cancellation if it makes blocking calls but otherwise is oblivious to start/stop requests of the service. The worker is designed to be created, by the service, and run repeatedly. In general the worker assumes it is run once so it doesn’t have any complicated logic for waiting or threading, that is the service’s job. Often the worker is created and tested outside the program altogether and the service overlay is added only when it is needed.


Creating a Simple Windows Service


For example purposes we will create a simple Windows service that ??. In order to create a service you need Visual Studio (2010+). If you are using Visual Studio Express then be sure to get the edition for Windows Desktop and not Windows 8. The Windows 8 version only supports Windows Store apps. Once you’ve gotten Visual Studio create a new project and select Windows\Windows Service to create the skeleton code (ex. MyService).


The generated project contains a fully working service that does nothing. The service host is contained in Program.cs (for C#) and contains the standard entry point that you might see in any console application. The project also contains a single service Service1.cs. Since this is skeleton code the service doesn’t actually do anything and therefore there is no service worker. We will now flush out a more useful service.


Implementing the Service Host


Inside the entry point method notice that it creates an array of service instances, 1 for each service that is defined in the project. The array of instances is then passed to ServiceBase.Run which allows the SCM to find them later. Technically the SCM already knows the host that each service is associated with. This call really just provides the SCM with the created instance. Notice that, as mentioned already, the service instances are created when the host starts even if the service itself is never told to start. This will influence what you want to do in the service constructor later on.


The downside to the default code is that it is not debuggable. The SCM starts the host, which calls the entry point, too early in the process to attach a debugger. Attempting to set a breakpoint, assert a failure or otherwise cause a break into the debugger will cause the SCM to fail the request. It is therefore important that the host do minimal work because there is no easy way to debug it if something goes wrong. Needless to say this is a hassle.


We can modify the host to allow for debugging by making a couple of changes to the entry point. What we are going to do is allow the host to run as either a service or a normal console application (with host-like behavior) based upon a simple command line argument. Before we do that though it is important to remember that services run in a sandbox that normal applications do not. While we can make debugging the host easier there are some problems that will simply not appear until running as a service. These generally include security-related issues, user context because of (or a lack thereof) logins and UI problems.


The first thing that needs to change is the project type. When a service is created by the project template it defaults to Windows Application. This is an odd choice since it cannot have a UI so change it to Console Application (project properties\Application\Output type). This seemingly minor change has one big effect, we can now call Console methods. Without this change Console methods are ignored.


Next we need to add support for command line arguments to the entry point by adding the standard parameter information to Main. We then need to parse the command line, if any, looking for some sort of indicator that we should run as a console application rather than a service. I tend to use a simple -console argument. Since we generally only run the host in "console" mode for debugging it makes sense to default to running as a normal service and only switching to console mode when given an argument. Here’s the logic to do that.


static ProgramSettings ParseCommandLine ( IEnumerable<string> args )
{
    var settings = new ProgramSettings();

    if (args != null)
    {
        foreach (var arg in args)
        {
            var token = arg.TrimStart('-', '/');

            if (String.Compare(token, "console", true) == 0)
                settings.RunAsConsole = true;
        };
    };

    return settings;
}

internal sealed class ProgramSettings
{
    public bool RunAsConsole { get; set; }
}

Whether we’re running as a service or console we will still create an instance of each service type. Rather than dealing with the array though we will simply create an instance of the service and use it. If we were going to host multiple services then the array would need to stay.


static void Main ( string[] args )
{
    var settings = ParseCommandLine(args);

    var service = new Service1();

    ServiceBase.Run(service);
}

The final piece is running the service or passing it on to the SCM. We will use the results of the command line parsing to determine which way to go. In the case of the SCM we will simply call ServiceBase.Run as we have before. But for a console application we need to mimic the SCM behavior as best we can. Here’s the code that I’ve found works well enough for most services.


if (settings.RunAsConsole)
{                
    service.Start(args);
    Console.CancelKeyPress += (o,e) => 
    {
        m_evtTerminate.Set();
    };

    Console.WriteLine("Press Ctrl+C or Ctrl+Break to quit");
    m_evtTerminate.WaitOne();

    service.Stop();
} else
    ServiceBase.Run(service);

The above code calls the Start method on the service, waits for it to finish, hooks into the termination handler for the console and waits for the user to press the termination key. When the user opts to terminate the service it tells the service to stop. This is similar to how the SCM works but there are differences. For one the SCM will fail the service if it takes too long to start or stop. We could implement that logic if we wanted but it is overkill in most cases since a slow startup will generally be obvious. Another difference is that you cannot pause or continue the service using the console. If this is important to your service then you can implement it using the console as a way to communicate with the service. Yet another difference is in error handling. When a service crashes the SCM will log the event and may try to restart the service. For debugging purposes this isn’t that useful so the console host doesn’t do any of that.


Implementing the Service Instance – OnStart


Now that the host is done we can move on to the service itself. As mentioned earlier every service should really just start the work in the OnStart. The constructor should do little, if any, work. The most you might want to do is create any fields you’ll need. Avoid creating any fields that are expensive or do not work well when living for long periods of time (ex. database connections).


Implementing OnStart is pretty straightforward. Since the method must return quickly no expensive or blocking calls can be made. This pretty much mandates using a worker thread. You could use the Task type to create a task but in most cases it doesn’t make a lot of sense. Tasks are backed by the thread pool (in most cases) and therefore are using threads behind the scenes. Being able to bounce between thread pool threads in a service is of limited value so creating a worker thread just makes sense.


protected override void OnStart ( string[] args )
{
    m_thread = new Thread(DoWork);
    m_thread.Start();
}

The worker thread is responsible for creating an instance of the service worker and then looping until told to terminate. It probably makes sense to create the worker once (as a local variable) so it can be initialized and then reused for the life of the service. But if, for some reason, the worker cannot run that long then creating it each time through the loop is also an option. In either case the thread will run until told to terminate. I find that the easiest way to implement this logic is using a ManualResetEvent. The event is a field in the service that is initially not signaled. The worker thread loops until the event is signaled and then terminates. The event will get signaled when the service is told to stop. To avoid using up all the CPU time in the case where the worker is not doing anything a polling interval should be used. So, effectively, the thread will do its work and then sleep for a fixed interval before repeating the work again, or told to stop. This provides the service lifetime management that the instance is responsible for.


private void DoWork ()
{
    m_evtTerminate = new ManualResetEvent(false);

    //TODO: Create an initialize worker, alternatively this can be done in OnStart if the service
    //should not start if the worker creation fails
    var worker = new WorkerService();

    do
    {
        worker.DoWork();
    } while (!m_evtTerminate.WaitOne(PollInterval));
}

private Thread m_thread;
private ManualResetEvent m_evtTerminate;

private const int PollInterval = 5000;

In this example I am calling the worker every 5 seconds but this can be changed to any value or even made configurable.  One issue with this implementation is that if the service worker needs to do some sort of initialization and that fails then the service probably cannot continue. An alternative approach would be for the start method to fail but since it is being done in the worker thread the service would start but then fail. Personally I find this OK but if you really need to fail the start if initialization fails then feel free to move the worker creation to the start method. But ensure that it doesn’t cause the start method to take too long.


Breather


Time to take a breather.  My blog is currently in a state of transition to a new platform.  I’m having to set back up styles and I’m waiting to bring files across.  As such I’m going to stop this post early.  Next time I’ll finish up the post and attach some sample code.

One Response to Windows Services Made Simpler, Part 2 Comments (RSS) Comments (RSS)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>