lexeme
lexeme

Reputation: 2973

The common practice for polling inside Windows services

Usually you're adviced to use something like that (using timeouts):

Thread workerThread = null;
AutoResetEvent finishedEvent = new AutoResetEvent(false);

protected override void OnStart(string[] args) {
    this.finishedEvent.Reset();
    this.workerThread = new Thread(this.Poll);
    this.workerThread.Start();
}

protected override void OnStop() {
    this.finishedEvent.Set();
    if(!this.workerThread.Join(2000)) {
        this.RequestAdditionalTime(5000);
    }
}

where Poll function is defined like:

private void Poll() {
    try {
        var timeout = Int32.Parse(ConfigurationManager.AppSettings["pollingTimeout"]);
        while(!this.finishedEvent.WaitOne(timeout, false)) {
            // do polling
        }
    }
    catch(Exception ex) { 
        Logger.Log.Fatal(ex); 
        throw; 
    }
}
  1. Are these constructs essentially equal:

    while(!this.finishedEvent.WaitOne(0, false))

    and

    while(true) with no finishedEvent?

  2. I've read that timeouts are used to reduce the cpu usage. Is it a bad choice to use polling without timeouts?

Upvotes: 4

Views: 4421

Answers (2)

Jeff Binnig
Jeff Binnig

Reputation: 79

What about putting a serialize around the do work and set a variable so when the o stop is called it will check the variable and cancel the stop if necessary?

Upvotes: 0

Matt Davis
Matt Davis

Reputation: 46034

There is a very simple way to do this providing you don't strictly need an orderly shutdown. If you mark the workerThread as a background thread, it will shut down automatically when the service stops. In this example, you can forego the use of the finishedEvent and use an infinite loop. For example,

Thread workerThread = null;

protected override void OnStart(string[] args)
{
    this.workerThread = new Thread(this.DoWork);
    // Signal the thread to stop automatically when the process exits.
    this.workerThread.IsBackground = true;
    this.workerThread.Start();
}

protected override void OnStop()
{
}

private void DoWork()
{
    try
    {
        while (true)
        {
            // do your work here...
        }
    }
    catch (Exception ex)
    {
        // handle exception here...
    }
}

Note that this approach should only be used if the work you are doing can be interrupted at any point without adverse effects. Let's say for an example that you were writing data to an Excel spreadsheet. Once the the Windows service exits, the thread represented by your DoWork() method will also exit immediately. If it is in the middle of adding data to the spreadsheet, it is very likely that the spreadsheet could have incomplete information or, worse case, might even be in a state that it cannot be opened in Excel. The point is, this approach could be used, but only in certain circumstances.

A better approach would be to fully transition to an event-based mechanism. It is much more efficient than polling, and it allows for an orderly shutdown to your service. Below is an example with comments.

Thread _workThread = null;
// I use ManualResetEvent instead of AutoResetEvent because I do NOT want
// this event to EVER reset.  It is meant to be set exactly one time.
ManualResetEvent _shutdownEvent = new ManualResetEvent(false);

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

protected override void OnStop()
{
    // Trigger the DoWork() method, i.e., the _workThread, to exit.
    _shutdownEvent.Set();

    // I always shutdown my service by simply joining the work thread.
    // There are probably more advanced techniques that take into account
    // longer shutdown cycles, but I design my worker thread(s) to have
    // tight work cycles so that the shutdownEvent is examined frequently
    // enough to facilitate timely shutdowns.
    _workThread.Join();
}

Now let's look at the details of the DoWork() method. For this example, I'm gonna use a timer to illustrate the event-based approach. Note that this illustration is not substantively different from calling the WaitOne() method with a timeout. However, if the work to be done involves processing input from other threads, e.g., a thread that receives data from a network socket or a thread that reads data from a database, this approach easily accommodates those scenarios.

// Creature of habit.  AutoResetEvent would probably work for this event,
// but I prefer to manually control when the event resets.
ManualResetEvent _timerElapsedEvent = new ManualResetEvent(false);
System.Timers.Timer _timer = null;

private void DoWork() {
    try {
        // Create, configure, and start the timer to elapse every second and
        // require a manual restart (again, I prefer the manual control).
        // Note when the timer elapses, it sets the _timerElapsedEvent.
        _timer = new Timer(1000) { AutoReset = false };
        _timer.Elapsed =+ (sender, e) => _timerElapsedEvent.Set();
        _timer.Start();

        // Create a WaitHandle array that contains the _shutdownEvent and
        // the _timerElapsedEvent...in that order!
        WaitHandle[] handles = new WaitHandle[] { _shutdownEvent, _timerElapsedEvent };

        // Employ the event-based mechanism.
        while (!_shutdownEvent.WaitOne(0)) {
            switch (WaitHandle.WaitAny(handles) {
                case 0:
                    // This case handles when the _shutdownEvent occurs,
                    // which will cause the while loop to exit.
                    break;
                case 1:
                    // This case handles when the _timerElapsedEvent occurs.
                    // Do the work, reset the event, and restart the timer.
                    DoProcessing();
                    _timerElapsedEvent.Reset();
                    _timer.Start();
                    break;
            }
        }
    } catch (Exception ex) {
        // handle exception here...
    }
}

The WaitHandle array is what makes the event-based mechanism possible. When you create the array, always be sure to add the events to the array in priority order. This is why the _shutdownEvent is listed before the _timerElapsedEvent. If the events were reversed in the array, it is possible that the _shutdownEvent would never get processed. You can add as many events to the WaitHandle array as necessary. That's what makes this approach so flexible.

Last thought. To facilitate timely shutdowns of your service, you want to make sure that work to be done when the _timerElapsedEvent is triggered doesn't take too long. In other words, the _shutdownEvent will not be checked by the while loop until the DoProcessing() method exits. So you'll want to limit the amount of time you spend inside the DoProcessing() method. If that method is long-running, then you'll probably want to check the _shutdownEvent inside DoProcessing() and exit at strategic points when the service has indicated it is shutting down.

Hope this helps.

Upvotes: 3

Related Questions