35
votes

I have a Windows service written in C# which is meant to perform a task every few minutes. I'm using a System.Timers.Timer for this but it doesn't ever appear to fire. I've looked at many different posts here on SO and elsewhere and I'm not seeing what is wrong with my code.

Here is my code, with non-timer related items removed for clarity...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Timers.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
            IntervalTimer = new System.Timers.Timer(60000);  // Default in case app.config is silent.
            IntervalTimer.Enabled = false;
            IntervalTimer.Elapsed += new ElapsedEventHandler(this.IntervalTimer_Elapsed);
        }

        protected override void OnStart(string[] args)
        {
            // Set up the timer...
            IntervalTimer.Enabled = false;
            IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
            // Start the timer and wait for the next work to be released...
            IntervalTimer.Start();
        }

        protected override void OnStop()
        {
            IntervalTimer.Enabled = false;
        }

        private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {   // Do the thing that needs doing every few minutes...
            DoWork();
        }
    }
}

I'm really scratching my head over this one. Can anybody spot what silly thing I'm getting wrong?

EDIT: By suggestion, I added IntervalTimer.Enabled = true; before IntervalTimer.Start(); in the service OnStart method. This doesn't resolve the issue.

I've added file trace logging into the service to confirm some of the internals and I know for sure that the Timer.Enabled value is true by the time OnStart() is finished.

6
You have to use a try/catch block in the Elapsed event handler. The timer class you are using is sucky this way, it swallows exceptions.Hans Passant
You may be experiencing the Microsoft Minute.jww

6 Answers

53
votes

Here is my work-around...

After way too many hours searching for an answer to this, I discovered a wide variety of articles and blogs discussing timers in Windows services. I've seen a lot of opinions on this and they all fall into three categories and in descending order of frequency:

  1. Don't use System.Windows.Forms.Timer because it won't work. (this only makes sense)

  2. Don't use System.Threading.Timer because it doesn't work, use System.Timers.Timer instead.

  3. Don't use System.Timers.Timer because it doesn't work, use System.Threading.Timer instead.

Based on this, I tried 2. This is also the approach that seems to be recommended by Microsoft since they say that System.Timers.Timer is suited to "Server applications".

What I've found is that System.Timers.Timer just doesn't work in my Windows Service application. Therefore I've switched over to System.Threading.Timer. It's a nuisance since it requires some refactoring to make it work.

This is approximately what my working code looks like now...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Threading.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            TimeSpan tsInterval = new TimeSpan(0, 0, Properties.Settings.Default.PollingFreqInSec);
            IntervalTimer = new System.Threading.Timer(
                new System.Threading.TimerCallback(IntervalTimer_Elapsed)
                , null, tsInterval, tsInterval);
        }

        protected override void OnStop()
        {
            IntervalTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            IntervalTimer.Dispose();
            IntervalTimer = null;
        }

        private void IntervalTimer_Elapsed(object state)
        {   // Do the thing that needs doing every few minutes...
            // (Omitted for simplicity is sentinel logic to prevent re-entering
            //  DoWork() if the previous "tick" has for some reason not completed.)
            DoWork();
        }
    }
}

I hate the "Doctor, doctor, it hurts when I do this..." solution, but that's what I had to resort to. One more opinion on the pile for the next guy with this problem...

10
votes

You forget to enable timer by setting:

IntervalTimer.Enabled = true;

or calling Start method:

IntervalTimer.Start();
protected override void OnStart(string[] args)
{
    // Set up the timer...
    IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
    // Start the timer and wait for the next work to be released...
    IntervalTimer.Start();
}
8
votes

Apparently, System.Timers.Timer hides any exceptions, swallows them quietly, and then chokes. Of course, you can handle these in your method that you've added as a handler to your timer, but if the exception is thrown immediately on entrance (before the first line of code is executed, which can happen if your method declares a variable that uses an object in a strong-named DLL of which you have the wrong version, for instance), you are never going to see that exception.

And you are going to join us all in tearing your hair out.

Or you could do this:

  • create a wrapper method that (in a try-catch loop) calls the method you would like to have executed. If this method is dying on you, the wrapped method can do the exception handling, without killing the timer, because if you do not stop the timer, it will never notice something went wrong.

(I did end up stopping the timer, because if it fails, trying again makes no sense for this particular application...)

Hope this helps those who landed here from Google (as did I).

5
votes

I also had to switch to System.Threading.Timer. To make re-factoring easier and others live easy, I created a separate class, containing an instance of System.Threading.Timer and has almost the same methods as System.Timers.Timer, so that calling code requires minimal changes:

/// <summary>
/// Custom Timer class, that is actually a wrapper over System.Threading.Timer
/// </summary>
/// <seealso cref="System.IDisposable" />
internal class Timer : IDisposable
{
    System.Threading.Timer _timer;

    public Timer()
    {

    }
    public Timer(int interval) : this()
    {
        this.Interval = interval;
    }

    public bool AutoReset { get; set; }
    public bool Enabled { get; set; }
    public int Interval { get; set; }
    public Action<object> OnTimer { get; internal set; }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            _timer.Dispose();
            _timer = null;
        }
    }

    public void Start()
    {
        _timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(OnTimer), null, 0, Interval);
    }
    public void Stop()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        }
    }
}

I hope this will help!

4
votes

To add to what "user1820848" wrote, because that was my problem as well, if your System.timers.timer elapsed event doesn't seem to be firing, put everything in the event handler in a try/catch block, and look for any problem there. I tried all of the recommended methods to deal with this problem (or thought I had), including switching from system.timers.timer to system.threading.timer, and that didn't work either.

I think the problem is compounded because many of us are moving our applications from our workstation, where we can attach to the running service and verify that it works, to a server where we don't have any debugging support. So you're stuck with event log messages or tracelistener messages, and it's completely odd that the event doesn't fire.

I had a situation where I have three running services on this server, running essentially the same timer code. I even went line by line with another running service's code to make sure I was doing the system.timers.timer handling the same. But the other service works fine, and this one didn't seem to be firing the event at all.

The problem, as it turned out, was that in my initial dim statements I was firing up a class that was trying to connect to Oracle. That call was failing, but it was actually failing because the Oracle client version on my workstation and server was slightly different. It happened when the CLR was resolving the references, so it wasn't caught in my underlying class try/catch blocks. If I were debugging, the debugger would have flagged the error. Running on the server, the CLR had no way to tell me about the problem. So my service just sat there on an untrapped error.

Putting everything in a try/catch immediately pointed out the problem. Put your try before any declarations in that subroutine. If you're failing on a very early statement, that's how you'll catch it.

[Sorry for the separate answer, but you have to provide answers to get enough reputation to even comment on someone else's answer?!?]

[Edit: another thing to try is take your code out of the timer event, put it into another sub/function, call that from your startup code, and also put the function call in your timer event. Weeks later, back at my workstation, trying to run the same code, and I have that sinking feeling that my timer event isn't getting called, and I've been here before. Indeed! But putting everything in a try/catch isn't working either!?! Moved it to a function call and Bam, there's my exception - Oracle again. But it wasn't coming up even with every single line inside a try/catch, until I moved the code out of the timer event and tried again.]

-2
votes
 private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {   // Do the thing that needs doing every few minutes...
        DoWork();

        //Add following 2 lines. It will work.
        **IntervalTimer.Interval= 100; //any value
        IntervalTimer.Start();**
    }