Windows Service System.Timers.Timer not firing

38,129

Solution 1

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...

Solution 2

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();
}

Solution 3

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).

Solution 4

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!

Solution 5

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.]

Share:
38,129
Joel Brown
Author by

Joel Brown

I'm a professional software developer with more than twenty five years of experience across many industries and the entire systems development lifecycle. I'm the principal consultant at Mooseware Limited.@joelabrowngooglefacebooklinkedin

Updated on July 25, 2020

Comments

  • Joel Brown
    Joel Brown almost 4 years

    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.

  • Joel Brown
    Joel Brown over 12 years
    That makes sense. I didn't have it originally because the MSDN library says about the Timer.Start method: "Starts raising the Elapsed event by setting Enabled to true." I've added this in, but it doesn't seem to make a difference. I will edit my question with some extra details.
  • LCJ
    LCJ about 10 years
    Thanks... The System.Threading.Timer is the only one that worked for me in Windows Server 2008 R2 though Timers.Timer worked in my Windows 7 laptop.
  • NibblyPig
    NibblyPig almost 10 years
    Excellent, I am one of those next guys..:)
  • the.Doc
    the.Doc over 8 years
    Taken from the link in your post: "Calling the Start method is the same as setting Enabled to true. Likewise, calling the Stop method is the same as setting Enabled to false."
  • Joel Brown
    Joel Brown almost 8 years
    Cool idea. Thanks!
  • OlafW
    OlafW over 7 years
    Maybe a little late but for me the solution was to change the target platform - we had a service-project set to x86 running on Server 2012 64-bit... switching to AnyCPU solved the issue. Somewhere I've stumbled over a comment that Server 2012 handles the timer threads differently to 2008 R2.
  • John Odom
    John Odom almost 6 years
    I noticed that you don't have AutoReset or Enabled used anywhere. Could you explain why?
  • AUR
    AUR almost 6 years
    @John I haven't shared the implementation of these two properties. The objective of Enable was to support enabling / disabling my timer instance, and AutoReset provides support for resetting the timer once fired.
  • Vladislav
    Vladislav over 4 years
    you saved my day! Never would have thought about this. This was the exact case for me - some dll can't be loaded and no exception until I manually called the Elapsed event handler!
  • Peter Huppertz
    Peter Huppertz over 4 years
    I only just saw this (I'm "user1820848" -- no idea how that happened, I see myself as my own name). The solution in your edit is in fact the exact same thing as the wrapper class I was on about. The reason why the wrapper class works is because the error occurs during the declaration of the Oracle client rather than during calling it.