我试图让每个分钟的计时器与系统时钟同步(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的问题?
答案 0 :(得分:2)
System.Timers.Timer
等正常计时器不准确,不足以达到1毫秒的间隔。
首先,它们的内部更新速率为10-15毫秒。其次,取决于系统,其他线程可能会运行~15毫秒延迟您的计时器,然后Windows强制它们屈服。
如果你想要比另一个线程中报告的定时器使用System.Diagnostics.Stopwatch
更精确,它可以从0.3毫秒开始,并与你的.NET环境集成。
另一种选择是使用多媒体时间(精确到1ms左右)。
无论哪种方式here都是关于这个问题的优秀教程。
打破它:
定时器漂移通常会给定时器增加延迟。但你看到相反的情况发生了。由于定时器没有毫秒精度(它们仅精确到15ms范围内),它们通常会以这种粒度发射。因此实际上在某些情况下在分钟标记之前几毫秒触发定时器(之后立即触发它)。如果你要求它只在新的一分钟开始,我会在几毫秒的等待时间内补充(5ms应该这样做)。
你的家用电脑不是那么快(这意味着它表现出与定时器处理程序有关的额外定时器漂移),并且通常会在下一秒触发事件。您的工作PC有时会设法快速处理计时器事件,以便记录59秒(我认为它被截断,可能是59.900~59.999)。如果机器是多芯的,也可能发生这种情况,因为没有线程延迟,并且计时器可以非常快速地启动。
这是导致计时器违规的原因。
答案 1 :(得分:2)
((60- now.Second)* 1000 - now.Millisecond)
这意味着如果现在。第二个碰巧是59,你的时间会在不到一秒的时间内再次发射。这就是你的奇怪结果的原因(计时器没有在恰好0秒的偏移处开始)。
让计时器每秒触发一次,将上一个日期/时间值保存在单独的变量中,并在第二部分更改时更新屏幕上的计时器,可能会更有效率。
答案 2 :(得分:2)
只需使用Ticks,而不是使用DateTime.Now并拉动各个部分。在开始时获取滴答,然后计算下一个计时器滴答应该是什么滴答。一旦发生计时器滴答,使用最后一个值来计算下一个值应该是什么。
示例强>:
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);
}
你可能会像你一样使用秒和毫秒来做到这一点。诀窍是有一个起点来计算(而不是确定到下一分钟的秒数)。如果原始问题中没有提到其他问题,例如timer_Elapsed中的代码可能需要更长时间才能运行,那么您将需要添加代码来处理此问题。
如果您需要其他帮助,请发表评论。否则请选择正确的答案。
答案 3 :(得分:0)
请记住,默认情况下,Windows Server设置为比客户端版本更愿意与后台任务共享资源,因此如果服务器运行许多后台任务,则可能会影响计时器的准确性。
您可以尝试暂时更改它以优先处理前台任务以查看是否会产生不同的结果 - 设置位于系统控制面板中的某个位置,您正在寻找两个单选按钮,一个表示“程序”,另一个表示“程序”说“背景服务”或类似的。
答案 4 :(得分:0)
另一个例子是使用System.Windows.Threading中的Timer。
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));
}
}