定期运行的Windows服务

23
我正在编写一个Windows服务程序,一旦启动就会每隔X小时运行一次。完成的过程相当密集,因此我想使用后台工作进程。我使用设置文件存储运行间隔时间和服务最后运行时间。
我不确定最佳实现方式 - 我希望该服务在空闲时占用尽可能少的资源,并且在运行时需要在后台工作者中运行、报告其工作内容,然后返回到空闲模式。
我考虑过使用两个后台工作者。第一个工作者将是服务的私有本地变量,运行类似以下内容:
while (true)
{
      //create new background worker and run

      Thread.Sleep(Settings.Default.SleepTimeHours * 3600000);
}

在循环的每个迭代中创建一个子工作线程,并在完成后销毁。为了支持取消操作,我认为我必须在服务中拥有第二个工作线程的本地实例,但如果进程当前未运行,则该实例将为null。当次要工作线程完成时,它将发送我的报告,设置设置文件中的最后运行时间,然后处理工作线程并将引用设置为null。

我想知道是否有更好的方法或最佳做法。

谢谢


对于那些建议使用任务计划程序的人 - 我提出了这个选项,但架构师希望它成为一个Windows服务。 - Josh
2
@Broken Bokken:两全其美:http://www.genericgeek.com/Enable-a-service-from-command-line ;) - JustLoren
13个回答

34

我通常使用一个计时器,在进程开始运行时停止计时器。

这里有一篇文章讲解如何做到这一点


然而,我相信这仍然会锁定计时器事件线程,这可能导致服务无法响应Start()、Stop()、Reset()等操作。服务应该能够在30秒内对这些操作做出响应,因此采用多线程非阻塞的方法会更好。 - Ian
1
再次死机,显示文本:“抱歉,我们正在进行数据库维护”。 - Ali Umair
@AlisterScott 链接中的图片无法加载。 - Manjay_TBAG

15

这不是一个很好的想法,因为您将在整个"SleepTimeHours"期间锁定线程,甚至无法停止服务。

您可以将此服务设计为休眠5秒钟,然后检查是否是回到工作的时间,如果不是,请再睡5秒钟(如果您需要停止服务,这将给您必要的响应性)。

或者:您最好只编写一个控制台应用程序,在Windows“计划任务”功能中安排它每x小时运行一次。这样,如果您的应用程序没有做任何事情,您就不会阻塞或使用任何系统资源......

Marc


1
与其使用 while(true),你可以添加一个 while(_isPolling)。这样,当你停止服务时,它就会停止,并且线程在最终唤醒时会完成 :) - David Kiff
我正要建议使用计划任务解决方案而不是服务。 - Philippe
我同意这个观点,计划任务和服务一样有用,而且你甚至可以勾选一个框,上面写着“如果已经有一个实例在运行,请不要启动新的实例”(以防再次启动的时间发生在你仍在处理上一批数据时)。如果你的服务大部分时间都处于空闲状态,那么你真的需要浪费资源吗?每个正在运行的进程都是易受攻击的。值得庆幸的是,谷歌从服务更新程序转向了计划任务更新程序,因为服务在99%(任意数字)的时间里都没有做任何事情。 - Joshua
@Joshua,您能提供一个使用定时任务的完整源代码示例吗? - PreguntonCojoneroCabrón

10

我曾经使用计时器,但是对于一个新项目,我现在正在使用Quartz.net,坦白地说,它的CronTrigger可以让你使用cron字符串触发作业,这太棒了。感谢您在这里的建议! - Marc

7
像这样的东西怎么样:

这样做如何:

    public class LongRunningService : ServiceBase
{
    System.Threading.Thread processThread;
    System.Timers.Timer timer;
    private Boolean Cancel;

    protected override void OnStart(string[] args)
    {
        timer = new Timer(Settings.Default.SleepTimeHours * 3600000);
        timer.Elapsed += new ElapsedEventHandler(timer_Tick);
        timer.Start();

        Cancel = false;
    }

    protected override void OnContinue()
    {
        timer.Start();
    }

    protected override void OnPause()
    {
        timer.Stop();
    }

    protected override void OnStop()
    {
        if (processThread.ThreadState == System.Threading.ThreadState.Running)
        {
            Cancel = true;
            // Give thread a chance to stop
            processThread.Join(500);
            processThread.Abort();
        }
    }

    void timer_Tick(object sender, EventArgs e)
    {
        processThread = new System.Threading.Thread(new ThreadStart(DoWork));
        processThread.Start();
    }

    private void DoWork()
    {
        try
        {
            while (!Cancel)
            {

            if (Cancel) { return; }
            // Do General Work

            System.Threading.Thread.BeginCriticalRegion();
            {
                // Do work that should not be aborted in here.
            }
            System.Threading.Thread.EndCriticalRegion();
            }

        }
        catch (System.Threading.ThreadAbortException tae)
        {
            // Clean up correctly to leave program in stable state.
        }
    }
}

我建议使用System.Timers.Timer类而不是System.Windows.Forms.Timer。Forms.Timer需要加载System.Windows.Forms.dll,这通常不是Windows服务所需的。 - Matt Davis
在DoWork()中,while (!Cancel) {}的目的是什么?执行如何会低于if (Cancel) {return;}?也许while循环的结束括号应该放在最后? - Ali
pug,是的,你说得对,while循环的结束括号应该放在EndCriticalRegion调用下面。我会更新示例代码以反映这一点。 - Ian
好的模式 Abort - PreguntonCojoneroCabrón
1
@PreguntonCojoneroCabrón 这是一个非常古老的答案,但是回到 Abort() 可以被改进,并且是为了说明而存在。我会考虑在 thread.join() 上允许更长的超时时间(Windows服务可能需要最多30秒才能停止),甚至在必要时考虑调用 ServiceBase.RequestAdditionalTime() - Ian
旧答案,无效的答案?现在是2018年,情况如何? - PreguntonCojoneroCabrón

5

使用基于System.Threading.WaitHandle的方法。

using System.Threading;
private Thread _thread;
private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
private ManualResetEvent _scheduleEvent = new ManualResetEvent(false);
private System.Timers.Timer _scheduleTimer = new System.Timers.Timer();
protected override void OnStart(string[] args)
{
    // Configure the timer.
    _scheduleTimer.AutoReset = false;
    _scheduleTimer.Interval = 120000; // 2 minutes in milliseconds
    _scheduleTimer.Elapsed += delegate { _scheduleEvent.Set(); }

    // Create the thread using anonymous method.
    _thread = new Thread( delegate() {
        // Create the WaitHandle array.
        WaitHandler[] handles = new WaitHandle[] {
            _shutdownEvent,
            _scheduleEvent
        };
        // Start the timer.
        _scheduleTimer.Start();
        // Wait for one of the events to occur.
        while (!_shutdownEvent.WaitOne(0)) {
            switch (WaitHandle.WaitAny(handles)) { 
               case 0:  // Shutdown Event
                   break;
               case 1:  // Schedule Event
                   _scheduleTimer.Stop();
                   _scheduleEvent.Reset();
                   ThreadPool.QueueUserWorkItem(PerformScheduledWork, null);
                   break;
               default:
                   _shutdownEvent.Set(); // should never occur
            }
        }
    } );
    _thread.IsBackground = true;
    _thread.Start();
}
protected override void OnStop()
{
    // Signal the thread to shutdown.
    _shutdownEvent.Set();
    // Give the thread 3 seconds to terminate.
    if (!_thread.Join(3000)) {
        _thread.Abort(); // not perferred, but the service is closing anyway
    }
}
private void PerformScheduledWork(object state)
{
    // Perform your work here, but be mindful of the _shutdownEvent in case
    // the service is shutting down.
    //
    // Reschedule the work to be performed.
     _scheduleTimer.Start();
}

现在,使用Tasks(TAP)吗? - PreguntonCojoneroCabrón

2
您真的不想使用后台工作者。当您在前台(如UI)有某些事情正在进行,同时您还希望在后台完成其他任务时,才会使用后台工作者。在您的情况下,没有前台处理,您只需要每隔一段时间运行一个单独的作业。
此外,不要费心使用睡眠等操作。相反,使用调度程序框架(例如Quartz.NET)定期唤醒它。这样,当您的服务启动时,它将初始化调度程序并在调度程序唤醒之前不执行任何操作。当调度程序唤醒时,它将调用您在初始化时告诉它要调用的对象的方法。
这样,服务在空闲时几乎不会消耗任何CPU资源,在需要时可以全力工作。

使用 Quartz 的完整示例? - PreguntonCojoneroCabrón

2

2

在Super User上,有人提出了类似的问题。您可以安装一个监控Windows服务的工具。像Service Hawk这样的工具可以帮助您保持服务运行,并允许您安排自动重启(可能在夜间),以使服务平稳运行。


1
在我的公司,我们依赖于Windows任务计划程序来启动服务,然后使服务自行关闭。这确保了我们的服务始终在正确的时间运行,并且调度系统将允许报告成功/失败等信息。

不确定您所说的“扩展”是什么意思。我们每晚大约有15个服务在运行,周末大约有30个服务在运行。您认为我们需要运行多少个服务?哈哈 - JustLoren

1
使用计时器来完成此操作。

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