I have a requirement to create a Windows Service (Windows Task Scheduler and third-party libraries/utilities like Quartz.net are not options for me) that runs an action once every hour, on the hour, for 12 hours beginning at 8:00 AM.
I've written code using the System.Threading.Timer that executes fine sometimes, but other times the callback is executed a few milliseconds early. I feel like this will be problematic down the line if I need to add/remove files from a directory. This is my first real foray into Windows Services and timers, so any help would be appreciated.
Here's my code:
private Timer timer;
private DateTime startTime; // Equals 8:00 AM
private DateTime endTime;
private Int32 interval; // Equals 1 hour
private Int32 duration; // Equals 12 hours
private DateTime nextRunTime;
private DateTime lastRunTime;
public ScheduleService()
{
InitializeComponent();
InitGlobalsFromConfigFile(); // This assigns values to the globals above.
}
protected override void OnStart(string[] args)
{
this.WriteToFile("Started {0}.");
this.StartService();
}
protected override void OnStop()
{
this.WriteToFile("Stopped {0}.");
timer.Dispose();
}
private void ScheduleService()
{
DateTime now = DateTime.Now;
DateTime runTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0, 0);
endTime = startTime.AddHours(duration);
if (now < startTime)
{
nextRunTime = startTime;
}
else if (now >= startTime && now < endTime)
{
nextRunTime = runTime.AddHours(interval);
}
else
{
nextRunTime = startTime.AddDays(1);
}
this.WriteToFile(string.Format("Now: {0}", DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
this.WriteToFile(string.Format("runTime: {0}", runTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
this.WriteToFile("Interval: " + interval.ToString() + " hour(s)");
this.WriteToFile("Duration: " + duration.ToString() + " hour(s)");
this.WriteToFile("Start Time: " + startTime.ToString());
this.WriteToFile("End Time: " + endTime.ToString());
this.WriteToFile(string.Format("Last Run Time: {0}", lastRunTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
this.WriteToFile(string.Format("Next Run Time: {0}", nextRunTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
try
{
timer = new Timer(new TimerCallback(ScheduleServiceCallback));
TimeSpan timeSpan = nextRunTime.Subtract(DateTime.Now);
int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);
this.WriteToFile("dueTime: " + dueTime.ToString());
this.WriteToFile("");
timer.Change(dueTime, Timeout.Infinite);
}
catch (Exception ex)
{
this.WriteToFile("Error on: {0} " + ex.Message + ex.StackTrace);
using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController("ScheduleService"))
{
serviceController.Stop();
}
}
}
private void ScheduleServiceCallback(object e)
{
this.WriteToFile("Log: {0}.");
lastRunTime = nextRunTime;
this.ScheduleService();
}
Here's a sample from the log:
Started 02/17/2016 09:22:33.59 AM.
[Log entry #1]
Now: 02/17/2016 09:22:33.59 AM
runTime: 02/17/2016 09:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 01/01/0001 12:00:00.00 AM
Next Run Time: 02/17/2016 10:00:00.00 AM
Updated Now: 02/17/2016 09:22:33.59 AM
dueTime: 2246403
[Log entry #2]
Log: 02/17/2016 09:59:59.87 AM.
Now: 02/17/2016 09:59:59.87 AM
runTime: 02/17/2016 09:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 02/17/2016 10:00:00.00 AM
Next Run Time: 02/17/2016 10:00:00.00 AM
Updated Now: 02/17/2016 09:59:59.88 AM
dueTime: 118
[Log entry #3]
Log: 02/17/2016 10:00:00.00 AM.
Now: 02/17/2016 10:00:00.00 AM
runTime: 02/17/2016 10:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 02/17/2016 10:00:00.00 AM
Next Run Time: 02/17/2016 11:00:00.00 AM
Updated Now: 02/17/2016 10:00:00.00 AM
dueTime: 3599994
Notice how in log entry #2 that nextRunTime
and the lastRunTime
are the same value and Now
is 09:59:59.87 AM instead of 10:00:00.00 AM. Then in log entry #3, it appears as though the callback runs again less than a second later. How can I ensure that the callback isn't run twice back to back because the nextRunTime
wasn't updated?
Thanks.
So your timings will never be perfect. You should just accept this truism. Windows isn't a real-time OS.
With regards to your problem, you could write an asynchronous method that takes care of most of your concerns:
and use it like