Wednesday, July 25, 2012

A Simple Framework for Windows Services 2 of 3

Source code for Service Framework posts.
In the previous post I covered the ExecutableServiceBase class that serves as the foundation class for a simple Windows Service framework. In this and the next post, I’ll cover classes that build on that to provide functionality that’s commonly desired in a Windows Service application.

ThreadServiceBase

This post covers ThreadServiceBase, which serves as the base class for services that run on their own thread from the time the service starts until it is stopped. Typically this type of service runs in a loop that only exits when signaled through the abortFlag that the service needs to stop. There will be a point in the loop where it blocks on something, like waiting for a message on a MessageQueue, or waiting for a socket connection, so it doesn't spin and burn CPU cycles. I’ll walk through this step by step assuming you’re starting with a new service project created with the Visual Studio Windows Service template. Most of the steps are common to all the different type of service base classes in this simple framework. In this walkthrough, we’ll be creating the ExampleThreadService.
  1. Add the SoftWx.ServiceProcess project to your service project’s solution.
  2. Add a reference to the SoftWx.ServiceProcess assembly in your service project.
  3. Edit your service project’s Application properties. Change Output Type from “Windows Application” to “Console Application”.
  4. Change the name of the service file from “Service1” to “ExampleThreadService”.
  5. Edit the Program.cs file. Add a “using” for SoftWx.ServiceProcess, and change the Main method as shown below.
using SoftWx.ServiceProcess;

namespace ServiceTest {
    class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main(string[] args) {
            ExecutableServiceBase.Run(args, new ExampleThreadService());
        }
    }
}

  1. Change the default ExampleThreadService properties by opening it in the component designer (usually the default action of double clicking the service file in Solution Explorer).  Change ServiceName to “ExampleThreadService”. Change CanPauseAndContinue to “true”.
  2. Edit the ExampleThreadService code. Add a “using” for System.Threading and SoftWx.ServiceProcess. Change the parent class from ServiceBase to ThreadServiceBase. Delete the OnStart and OnStop methods. Add the OnWork method. When done, the entire class source should look like the code below.
// Copyright ©2012 SoftWx, Inc.
// Released under the MIT License the text of which appears at the end of this file.
// <authors> Steve Hatchett

using System;
using System.Threading;
using SoftWx.ServiceProcess;

namespace ServiceTest {
    partial class ExampleThreadService : ThreadServiceBase {

        public ExampleThreadService() {
            InitializeComponent();
        }

        private void InitializeComponent() {
            // 
            // ExampleThreadService
            // 
            this.CanPauseAndContinue = true;
            this.ServiceName = "ExampleThreadService";

        }

        // work method that simulates a long running method (one that runs for
        // the duration of the service). Typically, this kind of method would 
        // not use Sleep, but would block on some trigger source such as reading
        // from a MessageQueue, or waiting for a socket connection (for example).
        protected override void OnWork(SignalToken abortFlag, SignalToken continueFlag) {
            while (!abortFlag.IsSet) {
                if (continueFlag.IsSet) {
                    // simulate doing some work by writing out a message
                    WriteLogEntry("ThreadWork time=" + DateTime.Now.ToString("hh:mm:ss ffff"),
                        System.Diagnostics.EventLogEntryType.Information);
                    System.Threading.Thread.Sleep(3 * 1000); // simulate blocking on something
                } else {
                    // we've been paused - wait for signal to either continue or abort
                    WaitHandle.WaitAny(new WaitHandle[] { continueFlag.WaitHandle, abortFlag.WaitHandle });
                }
            }
        }
    }
}
Of course, this is just example code to show how it works. In real life you’d be blocking on something, and doing some real work. The ThreadServiceBase class handles the OnStart, OnStop, OnPause, and OnContinue methods, and wraps their essence into two signal flags that operate much like .Net’s CancellationToken. Your code can react to stopping, starting, pausing and continuing by interacting with abortFlag and continueFlag. All the OnXXXXX methods defined in ServiceBase are virtual, so your ThreadServiceBase descendant class is free to override them if you need to do some special processing. Just be sure to also call base.OnXXXX so it can do it’s thing too.

To see all the rest of the steps not specific to this framework, see the Microsoft walkthrough.

In the code for ThreadServiceBase, you can see how it handles the basic housekeeping of starting/stopping and pausing/continuing the service. In the OnStart, it creates and starts the thread that will be the main work thread for the life of the service. In the OnStop, it attempts to stop gracefully, requesting additional time from the service control manager if needed. Ideally though, it's ideal if your work method can respond quickly to a change in the abortFlag and exit the work method quickly.
// Copyright ©2012 SoftWx, Inc.
// Released under the MIT License the text of which appears at the end of this file.
// version 1.3 - 7/25/2012
// <authors> Steve Hatchett

using System;
using System.Diagnostics;
using System.Threading;

namespace SoftWx.ServiceProcess {
    /// <summary>
    /// 
    /// </summary>
    public class ThreadServiceBase : ExecutableServiceBase {
        private readonly SignalSource abortFlag = new SignalSource(); // flag used to signal worker method we need to stop
        private readonly SignalSource continueFlag = new SignalSource(); // flag used to signal worker method we can continue (i.e. not paused)
        private Thread workThread;

        /// <summary>
        /// Create a new instance of ThreadService
        /// </summary>
        public ThreadServiceBase() {
            this.abortFlag.Set(false);
            this.continueFlag.Set(true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="abortFlag"></param>
        /// <param name="pauseFlag"></param>
        protected virtual void OnWork(SignalToken abortFlag, SignalToken pauseFlag) { }

        /// <summary>Start the work.</summary>
        protected override void OnStart(string[] args) {
            this.workThread = new Thread(new ThreadStart(Work));
            this.workThread.Start();
        }

        /// <summary>Stop the work.</summary>
        protected override void OnStop() {
            // indicate that we are stopping - this will signal
            // any worker code currently executing
            this.abortFlag.Set(true);
            var ticks = Environment.TickCount;

            // block until it's safe to exit
            bool cleanExit = true;
            while (!this.workThread.Join(10 * 1000)) {
                var elapsed = Environment.TickCount - ticks;
                if (elapsed >= MaxWaitSeconds * 1000) {
                    cleanExit = false;
                    break;
                }
                if (IsRunningAsService) {
                    this.RequestAdditionalTime(elapsed + (15 * 1000));
                }
                WriteLogEntry("Requesting additional time to stop", EventLogEntryType.Information);
            }
            ticks = Environment.TickCount - ticks;
            if (cleanExit) {
                WriteLogEntry("Worker method exited cleanly in " + ticks + " milliseconds", EventLogEntryType.Information);
            } else {
                WriteLogEntry("Worker method did not exit cleanly within " + ticks + " milliseconds", EventLogEntryType.Information);
            }
        }

        /// <summary>Pause the work.</summary>
        protected override void OnPause() {
            this.continueFlag.Set(false);
        }

        /// <summary>Continue the paused work.</summary>
        protected override void OnContinue() {
            this.continueFlag.Set(true);
        }

        private void Work() {
            try {
                OnWork(this.abortFlag.Token, this.continueFlag.Token);
            }
            catch (OperationCanceledException) { }
        }
    }
}

No comments:

Post a Comment