Adjustable service timer in C#
I'm trying to write a Windows service that runs indefinitely. Windows forms and background programs for Linux don't seem too bad but maybe I'm just horribly inept at Windows Services. Unlike some other sleep or timer related questions I've dug through here, the time to wake up or sleep can be a regular interval, but isn't always such. The program reads from some data files that may instruct it to change its own schedule and this must take effect as of its next wake up time. It seemed quite easy as a console program and behaved perfectly there:
while (true)
{
// Calculate next time to run.
DateTime nextRun = NextWakeup();
TimeSpan nextTime = nextRun - DateTime.Now;
int sleepMs = (int)nextTime.TotalMilliseconds;
// Sleep until scheduled time
System.Threading.Thread.Sleep(sleepMs);
// Do a code cycle of more stuff here...
}
However, when I try to run it as part of a service so that it continues to be active while the user is logged out, the Service Manager stubbornly refuses to start it. I get the lovely 1053 error, "The service did not respond to the start or control reque开发者_StackOverflow中文版st in a timely fashion."
A lot of answers to related questions here seem to suggest going with a timer at all costs over thread sleeping. If I did such a thing instead of the while/sleep combination, how would I go about changing the timer interval at each run? Or is this all perfectly fine and I'm going about setting up my service wrong?
Much thanks in advance!
Windows services must usually respond to a control request (ususally start/stop but also pause/resume) in 30seconds. This means that if you sleep the main thread in the OnStart
your service will return the error you refer to.
The way to resolve your issue is to do your work on a separate thread, where you're free to sleep the thread in the way you describe. Just start this thread in the services' OnStart
and you should be able to easily return within the 30 second limit.
As an aside, instead of while(true)
you should consider the service being stopped must also return in that 30 second limit. If you have a thread sat sleeping the service will not shut down properly without either Abort
ing the thread (bad) or providing some mechanism for properly exiting the thread. This is exactly why most people go with the polling approach; the service can both determine whether its time to run, or determine whether a stop request has taken place. As long as this poll freqency is <30s the service will always shut down properly.
If you want to use timers its quite easy to do. I'd use System.Timers.Timer and changing its interval is as easy as mytimer.Inverval = nextTime.Seconds
or similar.
I'd personally run the timer without AutoReset = false
(so it doesn't restart the timer automatically) and then every time it wakes up it runs your "dowork" and then at the end of the dowork you work out when you want it to run next, set the interval as appropriate and then call Start on your timer again.
Of course in your service your start method just sets up the first timer run and then returns so that the startup is nice and quick. On shutdown you just clean up your timer (stop and dispose and such like) and then just return. Nice and clean.
I think you might be looking for something like this:
static class ConsoleProgram
{
static void Main()
{
ServiceBase[] servicesToRun = new ServiceBase[] { new MyService(config, Logger) };
ServiceBase.Run(servicesToRun);
}
}
public partial class MyService : ServiceBase
{
private bool _stopped = true;
protected override void OnStart(string[] args)
{
StartTimer();
}
protected override void OnStop()
{
StopTimer();
}
public void StartTimer()
{
_stopped = false;
Timer t = new Timer(TimerProc);
// Calculate your desired interval here.
t.Change(_config.Interval, new TimeSpan(0, 0, 0, 0, -1));
}
public void StopTimer()
{
_stopped = true;
}
private void TimerProc(object state)
{
// The state object is the Timer object.
Timer t = (Timer) state;
t.Dispose();
ThreadPool.QueueUserWorkItem(DoWork);
if (!_stopped) {
StartTimer();
}
}
}
精彩评论