当编写Windows服务时,是否有最佳实践需要检查。
该服务(单线程)需要在指定的时间间隔内工作,目前我能想到的只有:
- 使用sleep(),然后在循环中检查时间?
- 使用TTimer?
有什么建议吗?
即使您的服务是单线程的,也不会真正影响其运行,因为服务的代码总是在不同的线程上下文中被调用:
服务管理器将启动、停止、暂停和恢复服务执行,并请求当前服务状态。
服务本身将至少有一个线程执行实际工作,该线程需要对服务管理器的请求做出反应,根据请求更改服务执行状态并返回所请求的信息。服务需要在合理的时间内对来自服务管理器的请求做出反应,否则它将认为服务已挂起并将其终止。因此,如果服务可能具有长时间执行或阻塞代码,则最好使用多个服务线程。
是否使用Sleep()或定时器消息还取决于服务线程中是否有消息泵可用。如果没有消息泵,则应使用Sleep()或定时器回调。如果您已经有了消息泵,因为您需要通过Windows消息与其他进程或线程通信,或者您需要进行OLE操作,则使用定时器消息可能是最简单的方法。
几年前,我编写了一个执行后台任务的定时服务,类似于Windows的at
或Unix的cron
功能。它并不使用VCL,只使用了一些基础类。服务的Run()方法如下所示:
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;
这个方法在一个循环中使用了Sleep(), 但可以使用更好的解决方案
while integer(GetMessage(Msg, HWND(0), 0, 0)) > 0 do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
使用这一方法也同样有效,可以利用Windows计时器消息。
这是否必须是一个服务?你是否可以在Windows中设置定期任务来完成这个功能?
我会使用睡眠功能。
这两个选项都没有确切的时间保证,但是睡眠功能可以将资源返还给其他进程。
在服务中永远不要使用 TTimer,因为它的行为可能不符合您的期望,并且它不是线程安全的。
在服务中,我总是使用自己的时间间隔变量,并在任务执行时间之间进行休眠。为了保持服务的响应性,我会短暂地休眠,通常为 1-2000 毫秒,然后在检查我的间隔以确定是否到达执行“任务”的时间之前处理消息。如果还没有到达时间,请返回睡眠状态,并在循环中再次检查。通过这种方式,您可以释放资源,并能够在下一个任务执行之前响应用户输入(停止、暂停)。
我在服务中经常使用类似这样的代码:
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;