【发布时间】:2009-07-02 09:17:35
【问题描述】:
只是检查编写 Windows 服务时是否有任何最佳实践。
Service(单线程)需要在指定的时间间隔工作,目前我只能想到:
- 使用 sleep(),然后循环检查时间?
- 使用 TTimer?
有什么建议吗?
【问题讨论】:
只是检查编写 Windows 服务时是否有任何最佳实践。
Service(单线程)需要在指定的时间间隔工作,目前我只能想到:
有什么建议吗?
【问题讨论】:
你的服务是单线程的并不重要,因为服务的代码总是在不同的线程上下文中调用:
Service Manager 将启动、停止、暂停和恢复服务执行,并请求当前服务状态。
服务本身至少会有一个线程在做真正的工作,需要对服务管理器的请求做出反应,根据请求改变服务执行状态,并返回请求的信息。服务需要在相当短的时间内对来自服务管理器的请求做出反应,否则它会认为服务被挂起并杀死它。这就是为什么 - 如果服务可能有长时间执行或阻塞的代码 - 最好有多个服务线程。
是否使用 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 中设置计划任务?
【讨论】:
我会使用睡眠。
这两个选项都没有确切的时间保证,但 sleep 会将资源返回给其他进程。
【讨论】:
TTimer 不是线程安全的。如果您必须在线程中使用类似 TTimer 的方法,我建议来自 DSiWin32 的 TDSiTimer。
【讨论】:
永远不要在服务中使用 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;
当然,我在大多数地方都有一些 Try..Catch 以及写入日志和电子邮件.... 我已经使用这些技术运行了一年多的服务。 这绝不是规则。 请告诉我是否有更好的方法。 我一直在寻找提高我的 Delphi 知识的方法。 另外,如果我错过了发布问题的截止日期,我们深表歉意。
-特雷·奥亨博
【讨论】: