【问题标题】:Windows Service with System.Threading.Timer action for an interval within a range具有 System.Threading.Timer 操作的 Windows 服务,用于某个范围内的时间间隔
【发布时间】:2016-02-17 17:17:43
【问题描述】:

我需要创建一个 Windows 服务(Windows 任务计划程序和 Quartz.net 等第三方库/实用程序对我来说不是选项),它每小时运行一次操作,每小时运行一次,从 12 小时开始8:00 AM。

我使用 System.Threading.Timer 编写的代码有时可以正常执行,但有时回调会提前几毫秒执行。如果我需要从目录中添加/删除文件,我觉得这将是一个问题。这是我第一次真正涉足 Windows 服务和计时器,因此我们将不胜感激。

这是我的代码:

private Timer timer;
private DateTime startTime;       // Equals 8:00 AM
private DateTime endTime;
private Int32 interval;           // Equals 1 hour
private Int32 duration;           // Equals 12 hours
private DateTime nextRunTime;
private DateTime lastRunTime;

public ScheduleService()
{
    InitializeComponent();
    InitGlobalsFromConfigFile();  // This assigns values to the globals above.
}

protected override void OnStart(string[] args)
{
    this.WriteToFile("Started {0}.");
    this.StartService();
}

protected override void OnStop()
{
    this.WriteToFile("Stopped {0}.");
    timer.Dispose();
}

private void ScheduleService()
{
    DateTime now = DateTime.Now;
    DateTime runTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0, 0);

    endTime = startTime.AddHours(duration);

    if (now < startTime)
    {
        nextRunTime = startTime;
    }
    else if (now >= startTime && now < endTime)
    {
        nextRunTime = runTime.AddHours(interval);
    }
    else
    {
        nextRunTime = startTime.AddDays(1);
    }

    this.WriteToFile(string.Format("Now: {0}", DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
    this.WriteToFile(string.Format("runTime: {0}", runTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
    this.WriteToFile("Interval: " + interval.ToString() + " hour(s)");
    this.WriteToFile("Duration: " + duration.ToString() + " hour(s)");
    this.WriteToFile("Start Time: " + startTime.ToString());
    this.WriteToFile("End Time: " + endTime.ToString());
    this.WriteToFile(string.Format("Last Run Time: {0}", lastRunTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));
    this.WriteToFile(string.Format("Next Run Time: {0}", nextRunTime.ToString("MM/dd/yyyy hh:mm:ss.ff tt")));

    try
    {
        timer = new Timer(new TimerCallback(ScheduleServiceCallback));

        TimeSpan timeSpan = nextRunTime.Subtract(DateTime.Now);
        int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

        this.WriteToFile("dueTime: " + dueTime.ToString());
        this.WriteToFile("");

        timer.Change(dueTime, Timeout.Infinite);
    }
    catch (Exception ex)
    {
        this.WriteToFile("Error on: {0} " + ex.Message + ex.StackTrace);

        using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController("ScheduleService"))
        {
            serviceController.Stop();
        }
    }
}

private void ScheduleServiceCallback(object e)
{
    this.WriteToFile("Log: {0}.");
    lastRunTime = nextRunTime;
    this.ScheduleService();
}

这是日志中的一个示例:

Started 02/17/2016 09:22:33.59 AM.
[Log entry #1]
Now: 02/17/2016 09:22:33.59 AM
runTime: 02/17/2016 09:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 01/01/0001 12:00:00.00 AM
Next Run Time: 02/17/2016 10:00:00.00 AM
Updated Now: 02/17/2016 09:22:33.59 AM
dueTime: 2246403

[Log entry #2]
Log: 02/17/2016 09:59:59.87 AM.
Now: 02/17/2016 09:59:59.87 AM
runTime: 02/17/2016 09:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 02/17/2016 10:00:00.00 AM
Next Run Time: 02/17/2016 10:00:00.00 AM
Updated Now: 02/17/2016 09:59:59.88 AM
dueTime: 118

[Log entry #3]
Log: 02/17/2016 10:00:00.00 AM.
Now: 02/17/2016 10:00:00.00 AM
runTime: 02/17/2016 10:00:00.00 AM
Interval: 1 hour(s)
Duration: 12 hour(s)
Start Time: 2/17/2016 7:00:00 AM
End Time: 2/17/2016 7:00:00 PM
Last Run Time: 02/17/2016 10:00:00.00 AM
Next Run Time: 02/17/2016 11:00:00.00 AM
Updated Now: 02/17/2016 10:00:00.00 AM
dueTime: 3599994

请注意,在日志条目 #2 中,nextRunTime 和 lastRunTime 是相同的值,而 Now 是上午 09:59:59.87 而不是上午 10:00:00.00。然后在日志条目 #3 中,似乎回调在不到一秒后再次运行。如何确保回调不会因为 nextRunTime 未更新而连续运行两次?

谢谢。

【问题讨论】:

    标签: c# timer windows-services .net-4.5.2


    【解决方案1】:

    所以您的时间安排将永远完美。你应该接受这个真理。 Windows 不是实时操作系统。

    关于您的问题,您可以编写一个异步方法来解决您的大部分问题:

    async Task ScheduleAsync(Action action, 
                        DateTime startTime, 
                        TimeSpan interval,
                        CancellationToken cancellationToken = default(CancellationToken))
    {
        if(cancellationToken.IsCancellationRequested)
        {
            throw new TaskCanceledException();
        }
        var now = DateTime.UtcNow;
        if(now < startTime)
        {
            var delayTime = startTime - now;
            await Task.Delay(delayTime, cancellationToken);
            await ScheduleAsync(action, startTime, interval);
            return;
        }
        action();
        var nextTime = startTime+interval;
        await ScheduleAsync(action, nextTime, interval);
    }
    

    并像使用它

    //hold onto this, and cancel it when you shut down the service.
    var cancellationTokenSource = new CancellationTokenSource(); 
    
    ScheduleAsync(() => MyWork(), 
                  myDesiredStartTimeUtc, 
                  TimeSpan.FromHours(1), 
                  cancellationTokenSource.Token);
    

    【讨论】:

    • 对不起...我说的是类,但指的是命名空间。
    • 哎呀是的。 System.Threading.Tasks.Task in System.Threading.Tasks.
    • 顺便说一句,我接受了这样一个事实,即时间永远不会完美。 :) 我只是不希望回调在几毫秒内执行两次。
    • 我创建了一个小测试应用程序来试用您提供的代码。它突出显示ScheduleAsync(() =&gt; MyWork()... 并说:“因为没有等待此调用,所以在调用完成之前继续执行当前方法。考虑在调用结果中应用'await' 运算符。”我应该在代码中使用async ScheduleAsync(() =&gt; MyWork()... 吗?它会构建,但我很好奇如何解决警告。
    【解决方案2】:

    我同意 spender 的观点,这样你永远不会得到准确的结果。部分原因是因为事件接收器在大约正确的时间启动,但是,是否有其他线程或其他优先级引起 CPU 的注意是您无法控制的。

    按照建议,您可以使用异步方法,但是,这也依赖于消息接收器在正确的时间启动。如果您追求绝对精度,则需要启动自己的线程,该线程会循环直到您想要执行程序的时间。即便如此,如果 Windows 愿意,它也会在您的操作过程中抢先执行多任务处理。因此,即使启动发生在正确的时刻,也不能保证您的代码不会在中间被中断。

    更好的解决方案是在设计您的应用时考虑到不保证精度 - 只是近似值,并且可能会受到其他应用程序和硬件配置的影响。根据您的操作,您可能需要设置锁或互斥锁、线程等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-13
      • 1970-01-01
      • 1970-01-01
      • 2017-06-23
      • 2022-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多