如何安排C#Windows服务每天执行任务?


84

我有一个用C#(.NET 1.1)编写的服务,希望它在每晚的午夜执行一些清理操作。我必须将所有代码包含在服务中,那么最简单的方法是什么?使用Thread.Sleep()时间检查时间吗?


10
您真的想制作一个整天都位于内存中的.NET进程,并在午夜做些什么吗?您为什么在安装工具时不能设置一个系统事件,该事件在午夜(或其他可配置时间)触发,从而启动您的应用程序,执行该操作,然后消失?
罗伯特·P

6
我知道如果我发现自己有一项托管服务一直在消耗20-40兆的内存,并且一直在运行,那会很生气,除了每天一次,它什么都不做。:)
罗伯特·P

其重复的链接
csa

Answers:


86

我不会使用Thread.Sleep()。使用预定任务(如其他人所述),或在服务内部设置计时器,该计时器会定期触发(例如每10分钟触发一次),并检查日期是否自上次运行以来已更改:

private Timer _timer;
private DateTime _lastRun = DateTime.Now.AddDays(-1);

protected override void OnStart(string[] args)
{
    _timer = new Timer(10 * 60 * 1000); // every 10 minutes
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    _timer.Start();
    //...
}


private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // ignore the time, just compare the date
    if (_lastRun.Date < DateTime.Now.Date)
    {
        // stop the timer while we are running the cleanup task
        _timer.Stop();
        //
        // do cleanup stuff
        //
        _lastRun = DateTime.Now;
        _timer.Start();
    }
}

28
将计时器设置为在午夜到期而不是每分钟醒来检查时间是否更有意义?
Kibbee

11
@Kibbee:也许如果该服务不在午夜运行(出于某种原因),那么您可能希望在该服务再次运行时立即执行任务。
M4N

4
然后,您应该在服务的启动中添加一些代码,以弄清楚是否应该立即运行,因为该服务没有运行,应该运行。您不应该每10分钟连续检查一次。检查是否应该在启动时运行,然后确定下次是否运行。在那之后,并设置一个计时器,无论需要多长时间。
Kibbee

3
@Kibbee:是的,我同意(这是我们正在讨论的次要细节)。但是,即使计时器每10秒触发一次,它也不会造成任何危害(即不耗时)。
M4N

1
好的解决方案,但是上面的代码缺少_timer.start(),并且_lastRun应该设置为昨天,如下所示:private DateTime _lastRun = DateTime.Now.AddDays(-1);
Zulu Z

72

退房Quartz.NET。您可以在Windows服务中使用它。它允许您根据配置的时间表运行作业,甚至还支持简单的“ cron作业”语法。我已经取得了很多成功。

这是其用法的快速示例:

// Instantiate the Quartz.NET scheduler
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();

// Instantiate the JobDetail object passing in the type of your
// custom job class. Your class merely needs to implement a simple
// interface with a single method called "Execute".
var job = new JobDetail("job1", "group1", typeof(MyJobClass));

// Instantiate a trigger using the basic cron syntax.
// This tells it to run at 1AM every Monday - Friday.
var trigger = new CronTrigger(
    "trigger1", "group1", "job1", "group1", "0 0 1 ? * MON-FRI");

// Add the job to the scheduler
scheduler.AddJob(job, true);
scheduler.ScheduleJob(trigger);

2
一个好建议-一旦您执行了比基本Timer东西稍微复杂的事情,就应该看看Quartz。
serg10 2009年

2
Quartz非常易于使用,我认为即使对于超简单的任务,也要继续使用它。它将具有轻松调整计划的功能。
安迪·怀特2009年

超级简单不是真的吗?大部分新手都会在这里赶上这个问题stackoverflow.com/questions/24628372/...
周华健

21

日常任务?听起来这应该只是计划任务(控制面板)-此处不需要服务。


15

是否必须是实际服务?您可以只使用Windows控制面板中的内置计划任务吗?


由于该服务已经存在,因此在其中添加功能可能会更容易。
M4N

1
将诸如此类的狭义服务转换为控制台应用程序应该很简单。
斯蒂芬·马丁

7
他已经说过:“我必须保留服务中包含的所有代码”。我认为没有必要质疑原始要求。这要看情况,但是有时有很好的理由在计划任务上使用服务。
jeremcc

3
+1:因为您一直都需要从各个方面看待您的问题。
GvS

6
如果我确实每天只有一个任务要运行,那么我猜想一个带有计划任务的简单控制台应用程序运行就可以了。我的观点是,这完全取决于情况,并且说“不要使用Windows服务”对IMO来说不是一个有用的答案。
jeremcc

3

我完成此操作的方法是使用计时器。

运行服务器计时器,让它每60秒检查小时/分钟。

如果正确的小时/分钟,则运行您的过程。

实际上,我已经将其抽象为一个称为OnceADayRunner的基类。

让我稍微整理一下代码,然后将其发布在这里。

    private void OnceADayRunnerTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        using (NDC.Push(GetType().Name))
        {
            try
            {
                log.DebugFormat("Checking if it's time to process at: {0}", e.SignalTime);
                log.DebugFormat("IsTestMode: {0}", IsTestMode);

                if ((e.SignalTime.Minute == MinuteToCheck && e.SignalTime.Hour == HourToCheck) || IsTestMode)
                {
                    log.InfoFormat("Processing at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                    OnceADayTimer.Enabled = false;
                    OnceADayMethod();
                    OnceADayTimer.Enabled = true;

                    IsTestMode = false;
                }
                else
                {
                    log.DebugFormat("Not correct time at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                }
            }
            catch (Exception ex)
            {
                OnceADayTimer.Enabled = true;
                log.Error(ex.ToString());
            }

            OnceADayTimer.Start();
        }
    }

该方法的主要内容是在e.SignalTime.Minute / Hour检查中。

那里有钩子可以进行测试,等等。但这就是您的计时器已使所有工作正常运行的样子。


由于服务器繁忙而导致的轻微延迟可能导致其错过小时+分钟。
乔恩·B

1
真正。当我这样做时,我检查了:现在的时间是否大于00:00?如果是这样,距我上次执行工作已经超过24小时了吗?如果是这样,请运行该作业,否则再次等待。
ZombieSheep

2

正如其他人已经写过的,在您描述的场景中,计时器是最好的选择。

根据您的确切要求,可能不必每分钟检查一次当前时间。如果您不需要精确地在午夜执行操作,而只是在午夜之后一小时内执行操作,则可以采用马丁的方法,即仅检查日期是否已更改。

如果您希望在午夜执行操作的原因是希望计算机上的工作负荷较低,请多加注意:别人经常做出相同的假设,突然您有100个清理操作在0:00和0之间启动。 :01上午

在这种情况下,您应该考虑在其他时间开始清理。我通常不是在时钟时间,而是在半小时(我个人偏爱1.30)来做这些事情。


1

我建议您使用计时器,但将其设置为每45秒而不是每分钟检查一次。否则,您可能会遇到繁重的工作,错过了对特定分钟的检查,因为在计时器触发与代码运行并检查当前时间之间,您可能错过了目标分钟。


如果您不检查并确保它尚未运行过一次,则如果检查45秒,则有可能两次击中00:00。
Michael Meadows,

好点子。通过在检查过程结束时调用Sleep(15000)可以避免此问题。(或者可能是16000 ms,只是为了安全起见;-)
Treb

我同意,无论您选择哪种计时器持续时间,都应该检查它是否已经运行。
GWLlosa,


0

对于发现上述解决方案不起作用的用户,这是因为您可能this在类的内部,这暗示了一种扩展方法,正如错误消息所述,该方法仅对非通用静态类有意义。您的课程不是静态的。作为扩展方法,这似乎没有什么意义,因为它作用在所讨论的实例上,因此请删除this


-2

试试这个:

public partial class Service : ServiceBase
{
    private Timer timer;
    public Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        SetTimer();
    }

    private void SetTimer()
    {
        if (timer == null)
        {
            timer = new Timer();
            timer.AutoReset = true;
            timer.Interval = 60000 * Convert.ToDouble(ConfigurationManager.AppSettings["IntervalMinutes"]);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }
    }

    private void timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        //Do some thing logic here
    }

    protected override void OnStop()
    {
        // disposed all service objects
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.