Friday 21 May 2010

Recursive Timers Not Firing

Wasted a good while today trying to find out why some Timers I had set up kept stopping firing. Here's the scenario: I have a windows service that does 2 things.
1) Generate Alerts: Periodically generate a batch of alert emails to send to all users of a system reminding them of pending tasks they have assigned to them.
2) Send Emails: For all emails in a database table, send them.

Generate Alerts parses a cron format string to determine how often to run. Send Emails has an interval in milliseconds. One important point here is that I don't want these tasks to run every X seconds. I want them to run after X seconds and then again X seconds after the previous run has completed. Hence I don't want to use a Timer in the standard way as one execution could begin while another is still in progress.

For this reason, in each case I create a timer to do my work, and then at the end of the timer I create another timer. Note these are the Timers from System.Threading and are configured to run only once, not at every tick). Here's an example:


public void StartEmailSender(int waitTime, int retryAttempts)
{
log.Debug("Starting EmailSender");
WaitTime = new TimeSpan(0, 0, 0, 0, waitTime);
RetryAttempts = retryAttempts;

//Set the SendQueuedEmails method to run after the elapsed time.
emailSenderTimer = new Timer(delegate(object s)
{
SendQueuedEmails();
}
, null, WaitTime, new TimeSpan(-1));
log.Debug("EmailSender Started");
}

public void SendQueuedEmails()
{
log.Debug("Entering SendQueuedEmails method");

//Code to send emails goes here

//Create a new timer to run this method again after the elapsed time.
emailSenderTimer = new Timer(delegate(object s)
{
SendQueuedEmails();
}
, null, WaitTime, new TimeSpan(-1));

log.Debug("Exiting SendQueuedEmails method");
}


The original problem I was having is that the timer would run X number of times and then stop. Or one of the 2 timers would continue for longer than the other but then eventually stop. I discovered the reason was that my Timers were being Garbage Collected.

The above code is the working version. In my previous version which did not work I was not assigning the output of "new Timer(delegate(object s)" to anything. Now it gets set to a private class member. This ensures that there is always a reference to the current Timer so long as the class has not been disposed. I ensure the class is not disposed by again having a private class member in the service's main class which I initialise in the OnStart method. Hence the class is not disposed until the service is stopped and hence there is always a reference to the current Timer which is waiting to execute, and only the ones which have already completed get disposed.

No comments:

Post a Comment