views:

823

answers:

6

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:

  1. Use sleep(), then check the time in a loop?
  2. Use a TTimer?

Any advice?

+3  A: 

I would use the sleep.

Both options have no exact time guarantee, but sleep gives resources back to other processes.

Gamecat
Timer messages would be used with a message loop, and GetMessage() does block if there are no messages in the queue. Resources are not wasted either way.
mghie
+10  A: 

Does this need to be a service? Could you maybe setup a scheduled task in Windows?

Jamie
First question that came to mind as well. http://weblogs.asp.net/jgalloway/archive/2005/10/24/428303.aspx
ilitirit
An interesting read. It has very valid points, and so do some of the comments. I like this quote especially: "better to use an existing service than to roll your own that does the same thing. But... Windows Task Scheduler sucks." So yes, Task Scheduler should be first thing to try, and writing a service should only be done in extreme cases. But these cases exist, I wrote a scheduling service only after having battled the task scheduler service for a long time and finally had to accept that it just wasn't versatile enough.
mghie
+10  A: 

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.

mghie
+1 great answer
Cobus Kruger
I'd avoid the message loop unless you really need it. The first loop is basically what I do, though in a thread.
mj2008
+3  A: 

TTimer is not threadsafe. If you have to use TTimer-like approach in a thread, I'd suggest TDSiTimer from DSiWin32.

gabr
+2  A: 

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.

Jason T
A: 

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

InvisibleMan1002