【问题标题】:Windows Service: Do work at specified times (Delphi)Windows 服务:在指定时间工作 (Delphi)
【发布时间】:2009-07-02 09:17:35
【问题描述】:

只是检查编写 Windows 服务时是否有任何最佳实践。

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

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

有什么建议吗?

【问题讨论】:

    标签: windows delphi winapi


    【解决方案1】:

    你的服务是单线程的并不重要,因为服务的代码总是在不同的线程上下文中调用:

    • 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 计时器消息。

    【讨论】:

    • 除非你真的需要,否则我会避免消息循环。第一个循环基本上就是我所做的,虽然在一个线程中。
    【解决方案2】:

    这需要成为一项服务吗?您能否在 Windows 中设置计划任务?

    【讨论】:

    • 一本有趣的书。它有非常有效的点,一些 cmets 也是如此。我特别喜欢这句话:“最好使用现有的服务而不是推出自己的服务来做同样的事情。但是...... Windows 任务计划程序很烂。”所以是的,任务计划程序应该是首先尝试的事情,并且只应该在极端情况下编写服务。但是这些情况是存在的,我是在与任务调度服务斗争了很长时间之后才写了一个调度服务,最终不得不承认它的功能不够全面。
    【解决方案3】:

    我会使用睡眠。

    这两个选项都没有确切的时间保证,但 sleep 会将资源返回给其他进程。

    【讨论】:

    • 定时器消息将与消息循环一起使用,如果队列中没有消息,GetMessage() 会阻塞。无论哪种方式都不会浪费资源。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      永远不要在服务中使用 TTimer,它的行为不会总是如你所愿,而且它不是线程安全的。

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

      【讨论】:

        【解决方案6】:

        我总是在服务中使用这样的东西:

        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 知识的方法。 另外,如果我错过了发布问题的截止日期,我们深表歉意。

        -特雷·奥亨博

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-23
          • 1970-01-01
          • 2016-03-28
          • 1970-01-01
          • 2012-05-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多