System.Timers.Timer doesn’t seem to fire

If you are using System.Timers.Timer to fire off a work loop at a set interval, you may sometimes come across the situation that it seems as if your timer’s Elapsed event doesn’t fire – you know that the timer’s interval has expired, but nothing happens.

I’ve been tearing my hair out on a few occasions about this. Usually, if I would do a rebuild, the problem would automagically disappear, but today it didn’t.

Apparently, I was not the only one suffering from this problem, because Stack Overflow had various threads inquiring about this. Most of the answers pointed to “use system.threading.timer instead”. One of them actually hinted at the problem: if the event handler would throw an exception, system.timers.timer would quietly swallow this exception and then choke.

You might say “yes, but I do exception handling in my handler… so the exception should be caught in the handler itself rather than be thrown up to the calling timer… right?”

Most of the time, right. But if calling your handler immediately results in an exception, such as when your handler declares an object from, say, a DLL and it cannot find the correct version of said DLL, you are toast. Even if the first statement in your method is a Try, this is never hit – the simple act of calling the method results in an exception. Which is thrown back up to the caller, which just so happens to be your misbehaving timer.

Unfortunately, using System.Threading.Timer is not really an option. First, it is a lot more cumbersome to use than System.Timers.Timer… and secondly (and more importantly), it does NOT work across threads.

So I went down for a smoke, which for me is a proven method to find my way around a problem.

When I came back up, this is what I did:

I wrote a wrapper method around my original handler. The wrapper method simply calls the original handler, from a try… catch block. And then I changed the handler for the timer’s Elapsed event to point to the wrapper rather than to the original worker method. A simple solution, really… it is fairly transparent, and it does not require a lot of refactoring. And because we stick to System.Timers.Timer, is is thread-safe. Most of the work went into typing in the comment lines – I believe in the concept of “don’t comment on HOW you do something, that should be clear when looking at the code, but do comment on WHY you chose to do it like this”, and with that in mind, this did require some commenting.

If the original handler throws an exception, this is handled by the wrapper rather than by your timer, and you can take proper care of it in the wrapper method, and your timer never notices it.

So, here’s what it looks like now. First, in my initialisation code, I instantiate the timer:

try {
    myTimer = new System.Timers.Timer();
    myTimer.Interval = PollInterval * 1000;
    myTimer.Elapsed += DoStuffWrapper;
    myTimer.Enabled = true;
    myTimer.AutoReset = true;
    AddLogEntry("Starting work loop in " + PollInterval + " seconds.", EventLevel.Debug, EventID.GeneralInfo);
} catch (Exception ex) {
    AddLogEntry("Timer not started: " + ex.Message, EventLevel.Critical, EventID.GeneralError);
}

The handler for the myTimer.Elapsed event is my wrapper, which looks like this:

public static void DoStuffWrapper()
{
    //The only raison d’être for this method is that it is a wrapper around DoStuff. If we would add the DoStuff method as the handler to our 
    //Timer.Elapsed event, and DoStuff would throw an error immediately on being invoked (referencing the wrong version of a DLL for instance),
    //you would NEVER see this, because the timer would eat up the exception and then choke silently. 
    try {
        DoStuff();
    } catch (Exception ex) {
        AddLogEntry("Work loop cannot be initiated - processing will stop (" + ex.Message + ")", EventLevel.Critical);
        myTimer.Enabled = false;
        return;
    }
}

This wrapper then calls the method DoStuff, where the actual work is done. If DoStuff throws an exception up the food chain, DoStuffWrapper will handle this.

 

Leave a Reply

Your email address will not be published. Required fields are marked *