views:

41

answers:

3

I'm trying to write a service in c# that should be run on a given interval (a timeout) from a given date. If the date is in the future the service should wait to start until the date time is reached.

Example:

  • If I set a timeout to be 1 hour from 21:00:00 I want the program to run every hour

  • If I set a timeout to be 1 hour from 3999.01.01 21:00:00 I want the program to until date and from then run each hour

I have sort of achieved that with the following code, but it has some problems!

  1. When I install the service (with installutil) the service is marked as starting because of the 'Thread.Sleep()'. This service appears to be hanging and is "installing" until started.

  2. The code inside 'ServiceTimer_Tick()' might take longer than the expected timeout. How can I prevent the timer stack from increasing if that happens?

Alternatives I've thought of :

  • include using the 'timeout.Interval' first time and then resetting it subsequent calls, but it doesn't feel right.

  • I've also considered ditching the entire service idea and compile it as a executable and set up a scheduled tasks.

Shortened example:

public Service()
{
    _timeout = new TimeSpan(0,1,0,0);
    _timer = new System.Timers.Timer();
    _timer.Interval = _timeout.TotalMilliseconds;
    _timer.Elapsed += new ElapsedEventHandler(ServiceTimer_Tick);            
}

private void ServiceTimer_Tick(object sender, System.Timers.ElapsedEventArgs e)
{            
    lock (_obj)  
    { 
        // Stuff that could take a lot of time
    }
}

public static void Main()
{
    Run(new Service());
}

protected override void OnStart(string[] args)
{
    long current = DateTime.Now.Ticks;
    long start = new DateTime(2010,9,15,21,0,0).Ticks;
    long timeout = _timeout.Ticks;
    long sleep;

    if (current > start)
        sleep = timeout - ((current % timeout)) + (start % timeout);
    else
        sleep = start - current;

    Thread.Sleep(new TimeSpan(sleep));

    _timer.AutoReset = true;
    _timer.Enabled = true;
    _timer.Start();
}
+1  A: 

Use a System.Threading.Timer. It supports both a dueTime and a period, just what you need.

Hans Passant
+1  A: 

This is easier with a System.Threading.Timer. You can tell it how long to wait before the first tick, and then how often to tick after that.

So, if you wanted to wait 2 days before starting, and then do something once per hour, you'd write:

Timer MyTimer = new Timer(TimerCallback, null, TimeSpan.FromHours(48), TimeSpan.FromHours(1));

That said, if this is something that only has to run once per hour, then it sounds like what you really want is an executable that you then schedule with Windows Task Scheduler.

Jim Mischel
+1  A: 

you have to move the timer logic to a separate thread that you spawn from your OnStart routine. Then your logic cannot interfere with the SCM and the service will start normally.

Edit: Just to elaborate - for this task I don't think timers work very well, since you are not taking clock corrections into account which could lead to a skew (or even be incorrect if the user manually changes the clock time). That's why comparing to the clock time in small intervals is imo preferred.

The Run routine of that thread could look like this:

    public void run()
    {
        while (processing)
        {
            //initiate action on every full hour
            if (DateTime.Now.Second == 0 && DateTime.Now.Minute == 0)
            {
                //Do something here
                DoSomething();
                //Make sure we sleep long enough that datetime.now.second > 0
                Thread.Sleep(1000);
            }
            Thread.Sleep(100);
        }
    }
BrokenGlass
This is definitively moving me in the right direction.. You read my question :)
Makach
Checking every 100 milliseconds if a full hour has been reached is a move in the wrong direction. Waiting is the job of a timer.
dtb
you can't just wait 5 hours and assume the clock has not been corrected/adjusted in the mean time, after all in the end you're interested in the clock time and not when your timer expires.
BrokenGlass
Then the application should probably listen for the [TimeChanged event](http://msdn.microsoft.com/en-us/library/microsoft.win32.systemevents.timechanged.aspx). Keeping a process running just to check for the current time is a waste of CPU cycles. It might even prevent the CPU from going into a sleep mode if the system is otherwise idle.
dtb
The Windows Scheduler will have all these concerns corner cases taken into consideration. Reinventing the wheel is probably not a good idea here.
dtb