11

Summary: Within a Windows service & Console Application I am calling a common library that contains a Timer that periodically triggers an action that takes around 30 seconds to complete. This works fine, however...

When a service stop or application exit is called and the timer is in the ElapsedEventHandler I need the service stop/application exit to wait until the event handler has completed.

I have implemented this functionality by having a Boolean InEvent property that is checked when the timer stop method is called.

While this is functional, the question is: Is this the best way to go about doing this? Is there an alternative approach that may serve this purpose better?

The other issue is that I need to avoid the service stop request failing with a "Service failed to respond to stop request"

This is my implementation

public sealed class TimedProcess : IDisposable
{
    static TimedProcess singletonInstance;
    bool InEvent;
    Timer processTimer;

    private TimedProcess()
    {
    }

    public static TimedProcess Instance
    {
        get
        {
            if (singletonInstance == null)
            {
                singletonInstance = new TimedProcess();
            }

            return singletonInstance;
        }
    }

    public void Start(double interval)
    {
        this.processTimer = new Timer();
        this.processTimer.AutoReset = false;
        this.processTimer.Interval = interval;
        this.processTimer.Elapsed += new ElapsedEventHandler(this.processTimer_Elapsed);
        this.processTimer.Enabled = true;
    }

    public void Stop()
    {
        if (processTimer != null)
        {
            while (InEvent)
            {
            }

            processTimer.Stop();
        }
    }

    void processTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            InEvent = true;
            // Do something here that takes ~30 seconds
        }
        catch
        {
        }
        finally
        {
            InEvent = false;
            processTimer.Enabled = true;
        }
    }

    public void Dispose()
    {
        if (processTimer != null)
        {
            Stop();
            processTimer.Dispose();
        }
    }
}

And this is how it is called in the service OnStart / console application main:

TimedProcess.Instance.Start(1000);

This is how it is called in service OnStop and application main (pending keypress):

TimedProcess.Instance.Stop();

3 Answers 3

11

Probably the easiest and most reliable way is to use a Monitor. Create an object that the main program and the timer callback can access:

private object _timerLock = new object();

Your main program tries to lock that before shutting down:

// wait for timer process to stop
Monitor.Enter(_timerLock);
// do shutdown tasks here

And your timer callback locks it, too:

void processTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    if (!Monitor.TryEnter(_timerLock))
    {
        // something has the lock. Probably shutting down.
        return;
    }
    try
    {
        // Do something here that takes ~30 seconds
    }
    finally
    {
        Monitor.Exit(_timerLock);
    }
}

The main program should never release the lock once it's obtained it.

If you want the main program to go ahead and shut down after some period of time, regardless of whether it's obtained the lock, use Monitor.TryEnter. For example, this will wait 15 seconds.

bool gotLock = Monitor.TryEnter(_timerLock, TimeSpan.FromSeconds(15));

The return value is true if it was able to obtain the lock.

By the way, I strongly suggest that you use System.Threading.Timer rather than System.Timers.Timer. The latter squashes exceptions, which can end up hiding bugs. If an exception occurs in your Elapsed event, it will never escape, meaning that you never know about it. See my blog post for more information.

3
  • Jim take a look at this (3rd note) from Microsoft: msdn.microsoft.com/en-us/library/System.Threading.Timer.aspx about the benefits of use System.Threading.Timer also for Server apps. You can get exceptions also when you are finishing. Jun 18, 2013 at 20:55
  • How would I go about putting a dialog up on the main thread indicating ot the user that this "background process" is in progress and when the main thread is able to close the project/application the dialog goes away?
    – Jake Smith
    Jun 26, 2014 at 17:15
  • @JakeSmith: You should post that as a new question. Jun 26, 2014 at 18:22
0

EDIT

Each callback to the System.Timers.Timer is queued on the ThreadPool. Be aware that the System.Timers.Timer can have a race condition (you can read more about it here.) System.Threading.Timer is a slightly nicer wrapper which I prefer to use due to it's simplicity.

You haven't described enough details to know if your particular application could handle that race condition, so it's hard to tell. But given your code, it is possible that there might be a callback queued up for processTimer_Elapsed after Stop() is called.


For the service timeout issue --

One way to do this is to make a call to the ServiceController method WaitForStatus with a timeout. I've done this in the past and it works reasonably well, although I recall there being some edge cases around waiting for a very long time.

See the MSDN reference. A sample use is described here.

2
  • Apologies, I failed to mention that the timed functionality is also called from a console application. I have edited the original question.
    – MrEyes
    Aug 12, 2011 at 16:31
  • You have it backwards. System.Timers.Timer wraps the System.Threading.Timer as the underlying timer object. It just adds some features on top of the System.Threading.Timer. The System.Timers.Timer adds a synchronization object for use by consumers. Oct 11, 2013 at 19:30
0

One possible alternative seems to be to not do the actual work in the timer callback itself but to just queue a work item from there on the tread pool to do the work. Then you can go ahead and dispose of the timer - anything currently running on the thread pool will remain operational, and your service can respond to the stop request immediately but the thread pool item (if queued) will still get processed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.