2

我试图让计时器每分钟运行一次,与系统时钟(00:01:00、00:02:00、00:03:00 等)同步。这是我的代码。

private System.Timers.Timer timer;

public frmMain()
{
    timer = new System.Timers.Timer();
    timer.AutoReset = false;
    timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    timer.Interval = GetInterval();
    timer.Start();
}

private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{

        System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString("hh:mm:ss tt"));
            timer.Interval = GetInterval();
            timer.Start();

}
private double GetInterval()
{
    DateTime now = DateTime.Now;
    return ((60 - now.Second) * 1000 - now.Millisecond);
}

它在我的家用电脑上完美运行。

12:12:00 AM
12:13:00 AM
12:14:00 AM
12:15:00 AM
12:16:00 AM
12:17:00 AM
12:18:00 AM
12:19:00 AM
12:20:00 AM
12:21:00 AM

但是,我在我的 VPS(Windows Server 2003)上得到了奇怪的结果。

12:11:59 AM
12:12:59 AM
12:13:00 AM
12:13:59 AM
12:14:00 AM
12:14:59 AM
12:15:00 AM
12:15:59 AM
12:16:00 AM
12:16:59 AM
12:17:00 AM
12:17:59 AM
12:18:00 AM
12:18:59 AM
12:19:00 AM
12:19:59 AM
12:20:00 AM
12:20:59 AM
12:21:00 AM

是因为 System.Timers.Timer 在 windows server 2003 上不能正常工作吗?还是我的 VPS 有问题?

4

5 回答 5

2

像这样的普通计时器System.Timers.Timer并不准确,也不足以达到 1 毫秒的间隔。

首先,它们的内部更新速率为 10-15 毫秒。其次,根据系统,其他线程可能会运行约 15 毫秒,从而在 Windows 强制它们让步之前延迟您的计时器。

如果您想要比另一个线程中报告的 Timer 使用更准确System.Diagnostics.Stopwatch,它可以从 0.3 毫秒开始,并与您的 .NET 环境集成。

另一种选择是使用多媒体时间(精确到 1ms 左右)。

无论哪种方式,这里都是关于这个问题的优秀教程。

分解它:

定时器漂移通常会增加定时器的延迟。但你看到相反的情况发生。由于计时器没有毫秒精度(它们仅精确到 15 毫秒范围内),它们通常会以该粒度被触发。因此,实际上在某些情况下会在分钟标记前几毫秒触发计时器(导致它在之后立即触发)。如果您要求它只在新的一分钟内触发,我会添加几毫秒的等待时间来补偿(5ms 应该这样做)。

您的家用电脑不是那么快(这意味着它会在处理计时器处理程序时表现出额外的计时器漂移)并且通常会在下一秒触发事件。您的工作 PC 有时会设法以足够快的速度处理计时器事件,以记录过去 59 秒(我确实认为它被截断,可能是 59.900 ~ 59.999)。如果机器是多核的,也可能发生这种情况,因为没有线程屈服延迟并且可以非常快速地触发计时器。

这就是您的 Timer 异常的原因。

于 2012-06-15T16:28:26.783 回答
2

((60 - now.Second) * 1000 - now.Millisecond)

这意味着如果 now.Second 恰好是 59,您的时间将在不到一秒的时间内再次触发。这就是你奇怪结果的原因(计时器没有在 0 秒偏移处触发)。

让计时器每秒触发一次,将先前的日期/时间值保存在单独的变量中,并在第二部分更改时更新屏幕计时器,这可能会更有效率。

于 2012-06-15T16:36:40.367 回答
2

而不是使用 DateTime.Now 并拉动各个部分,只需使用Ticks。开始时获取滴答声,然后计算下一个计时器滴答声的滴答声。一旦发生该计时器滴答,使用最后一个值来计算下一个值应该是什么。

示例

    private const long MILLISECOND_IN_MINUTE = 60 * 1000;
    private const long TICKS_IN_MILLISECOND = 10000;
    private const long TICKS_IN_MINUTE = MILLISECOND_IN_MINUTE * TICKS_IN_MILLISECOND;

    private System.Timers.Timer timer;
    private long nextIntervalTick;

    public void frmMain()
    {
        timer = new System.Timers.Timer();
        timer.AutoReset = false;
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.Interval = GetInitialInterval();
        timer.Start();
    }

    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

        System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString("hh:mm:ss tt"));
        timer.Interval = GetInterval();
        timer.Start();

    }
    private double GetInitialInterval()
    {
        DateTime now = DateTime.Now;
        double timeToNextMin = ((60 - now.Second) * 1000 - now.Millisecond) + 15;
        nextIntervalTick = now.Ticks + ((long)timeToNextMin * TICKS_IN_MILLISECOND);

        return timeToNextMin;
    }
    private double GetInterval()
    {
        nextIntervalTick += TICKS_IN_MINUTE;
        return TicksToMs(nextIntervalTick - DateTime.Now.Ticks);
    }
    private double TicksToMs(long ticks)
    {
        return (double)(ticks / TICKS_IN_MILLISECOND);
    }

您可能可以像以前一样使用 Seconds 和 Milliseconds 来做到这一点。诀窍是有一个起点来计算(而不是确定下一分钟的秒数)。如果原始问题中未提及其他问题,例如 timer_Elapsed 中的代码可能需要更长的时间才能运行,那么您将需要添加代码来处理此问题。

如果您需要其他帮助,请发表评论。否则请选择正确答案。

于 2012-06-15T16:42:21.597 回答
0

您好,另一个示例是使用 System.Windows.Threading 中的计时器。

using System;
using System.Windows.Threading;

namespace Yournamespace
{
    public partial class TestTimer
    {
        DispatcherTimer dispatcherTimer1m;
        public TestTimer()
        {
            dispatcherTimer1m = new  DispatcherTimer();
            dispatcherTimer1m.Tick += new EventHandler(DispatcherTimer1m_Tick);
            dispatcherTimer1m.Interval = TaskHelper.GetSyncIntervalms;
            dispatcherTimerm.Start();

        }

        private void DispatcherTimer1m_Tick(object sender, EventArgs e)
        {
            try
            {
            dispatcherTimer1m.Stop();           
            //Do your effort here
        }
        catch (Exception exc)
        {
                //Your exception handled here
        }
        finally
        {  
            dispatcherTimer1m.Interval = TaskHelper.GetSyncInterval1m;
            dispatcherTimer1m.Start();
            }
        }   
    }
    public class TaskHelper
    {
        private const ushort internalUpdate = 15;//ms        
        public static TimeSpan GetSyncInterval1m => new TimeSpan(0, 0, 0, 60,internalUpdate).Subtract( new TimeSpan(0, 0, 0, DateTime.Now.Second, 0));
    }
}
于 2019-07-31T14:49:32.380 回答
-1

请记住,Windows Server 默认设置为比客户端版本更愿意与后台任务共享资源,因此如果服务器正在运行大量后台任务,则计时器的准确性可能会受到影响。

您可以尝试暂时更改它以优先处理前台任务,以查看是否会产生不同的结果 - 该设置位于系统控制面板中的某处,您正在寻找两个单选按钮,一个显示“程序”,一个显示“后台”服务”或类似的。

于 2012-06-15T16:42:35.520 回答