Windows服务:在指定时间执行任务(Delphi)

6

当编写Windows服务时,是否有最佳实践需要检查。

该服务(单线程)需要在指定的时间间隔内工作,目前我能想到的只有:

  1. 使用sleep(),然后在循环中检查时间?
  2. 使用TTimer?

有什么建议吗?

6个回答

14

即使您的服务是单线程的,也不会真正影响其运行,因为服务的代码总是在不同的线程上下文中被调用:

  • 服务管理器将启动、停止、暂停和恢复服务执行,并请求当前服务状态。

  • 服务本身将至少有一个线程执行实际工作,该线程需要对服务管理器的请求做出反应,根据请求更改服务执行状态并返回所请求的信息。服务需要在合理的时间内对来自服务管理器的请求做出反应,否则它将认为服务已挂起并将其终止。因此,如果服务可能具有长时间执行或阻塞代码,则最好使用多个服务线程。

是否使用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计时器消息。


我会避免使用消息循环,除非你真的需要它。第一个循环基本上就是我所做的,只不过在一个线程中。 - mj2008

10

这是否必须是一个服务?你是否可以在Windows中设置定期任务来完成这个功能?


首先,我也想到了这个问题。http://weblogs.asp.net/jgalloway/archive/2005/10/24/428303.aspx - ilitirit
1
一篇有趣的阅读。它提出了非常有价值的观点,评论中也有。我特别喜欢这句话:“最好使用现有的服务而不是自己编写相同的功能。但是……Windows任务计划程序很糟糕。”所以,是的,任务计划程序应该是首选,只有在极端情况下才应该编写服务。但这种情况确实存在,我曾经与任务计划程序服务进行了长时间的抗争,最终不得不承认它的灵活性不够。 - mghie

3

我会使用睡眠功能。

这两个选项都没有确切的时间保证,但是睡眠功能可以将资源返还给其他进程。


1
计时器消息将与消息循环一起使用,如果队列中没有消息,则GetMessage()会阻塞。无论哪种方式,都不会浪费资源。 - mghie

3

TTimer不是线程安全的。如果你必须在一个线程中使用类似TTimer的方法,我建议使用DSiWin32中的TDSiTimer。


2

在服务中永远不要使用 TTimer,因为它的行为可能不符合您的期望,并且它不是线程安全的。

在服务中,我总是使用自己的时间间隔变量,并在任务执行时间之间进行休眠。为了保持服务的响应性,我会短暂地休眠,通常为 1-2000 毫秒,然后在检查我的间隔以确定是否到达执行“任务”的时间之前处理消息。如果还没有到达时间,请返回睡眠状态,并在循环中再次检查。通过这种方式,您可以释放资源,并能够在下一个任务执行之前响应用户输入(停止、暂停)。


0

我在服务中经常使用类似这样的代码:

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知识的方法。 另外,如果我错过了发布问题的截止日期,请原谅。-Trey Aughenbaugh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接