Just checking if there's any best practice when writing a Windows Service.
The Service (Single-thread) needs to work at specified time intervals, right now I can only think of:
- Use sleep(), then check the time in a loop?
- Use a TTimer?
Any advice?
Just checking if there's any best practice when writing a Windows Service.
The Service (Single-thread) needs to work at specified time intervals, right now I can only think of:
Any advice?
I would use the sleep.
Both options have no exact time guarantee, but sleep gives resources back to other processes.
Does this need to be a service? Could you maybe setup a scheduled task in Windows?
It doesn't really matter that your service is single-threaded, as a service will have its code always called in different thread contexts:
The Service Manager will start, stop, pause and resume the service execution, and request the current service state.
The service itself will have at least one thread doing the real work, which needs to react on the requests from the service manager, change the service execution state as requested, and return the requested information. A service needs to react to requests from the Service Manager in a reasonably short time, otherwise it will consider the service to be hung and kill it. That's why - if the service may have long-executing or blocking code - it may be better to have more than one service thread.
Whether to use Sleep() or timer messages does also depend on the availability of a message pump in the service threads. If you don't have a message pump you should use Sleep() or timer callbacks. If you have a message pump anyway, because you need to communicate with other processes or threads via Windows messages, or you need to do OLE stuff, then it may be easiest to use timer messages.
A few years ago I wrote a service for timed background execution of tasks, similar to the Windows at
or Unix cron
functionality. It doesn't use much of the VCL, only some base classes. The Run() method of the service looks like this:
procedure TScheduleService.Run;
var
RunState: TServiceRunState;
begin
while TRUE do begin
RunState := GetServiceRunState;
if (RunState = srsStopped) or (fEvent = nil) then
break;
if RunState = srsRunning then begin
PeriodicWork;
Sleep(500);
end else
fEvent.WaitFor(3000);
Lock;
if fServiceRunStateWanted <> srsNone then begin
fServiceRunState := fServiceRunStateWanted;
fServiceRunStateWanted := srsNone;
end;
Unlock;
end;
end;
This uses Sleep() in a loop, but a solution using
while integer(GetMessage(Msg, HWND(0), 0, 0)) > 0 do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
would work just as well, and then Windows timer messages could be used.
Never use a TTimer in a service, it will not always behave as you expect, and it is not threadsafe.
Within a service I always use my own time interval variables, and sleep in between task execution times. In order to keep the service responsive, I sleep for short periods, 1-2000 ms typically, and then process messages before checking my interval to know if it is time to execute the 'task'. If it is not yet time, go back to sleep, and check again after - in a loop. In this way, you give back resources, but are also able to respond to user input (Stop, Pause) before the next task executes.
I alwasy use something like this in a Service:
unit uCopy;
interface
uses
Windows, Messages,.......;
procedure MyTimerProc(hWindow : HWND; uMsg : cardinal; idEvent : cardinal; dwTime : DWORD); stdcall;
type
TFileCopy= class(TService)
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
VAR
timerID : UINT;
const
SECONDS = 900000;
procedure TFileCopy.ServiceStart(Sender: TService;
var Started: Boolean);
Begin
timerID := 0; //Probably not needed.
timerID := SetTimer(0, 1, SECONDS, @MyTimerProc);
End;
procedure MyTimerProc(hWindow : HWND; uMsg : cardinal; idEvent : cardinal;dwTime : DWORD); stdcall;
Begin
//Kill timer while trying.. If this function takes longer than the interval to run, I didn't want a build up. If that was possible.
KillTimer(0, timerID);
timerID := 0; //Is it needed?
//DO WORK.
{
//I was Connecting to a Network Drive, Minutes, seconds.....
//I only wanted to run this Every day at 2 AM.
//So I had my timer set to 15 minutes, once it got between 30 and 10 minutes of my 2 AM deadline,
//i killed the existing timer and started a new one at 60000.
//If it was within 10 minutes of my 2 AM, my interval changed to 500.
//This seems to work for me, my files get copied everyday at 2 AM.
}
//Work Complete. Start timer back up.
timerID := SetTimer(0, 1, SECONDS, @MyTimerProc);
End;
procedure TFileCopy.ServiceStop(Sender: TService;
var Stopped: Boolean);
Begin
if timerID > 0 then
KillTimer(0, timerID);
End;
Of course, I had some Try..Catch in most places along with writing to logs and emailing.... I've got a service running using these techniques for over a year now. By no means is it the rule. Please tell me if there is a better way. I'm always looking for ways to improve my Delphi knowledge. Also, sorry if I missed the deadline for posting to a question.
-Trey Aughenbaugh