DateTime.Now多久更新一次? 或者是否有更准确的API来获取当前时间?

我有一个循环运行的代码,它根据当前时间保存状态。 有时这可能只相差几毫秒,但由于某些原因,似乎DateTime.Now将始终返回至少10 ms的值,即使它仅在2或3 ms之后。 这是一个主要问题,因为我保存的状态取决于它保存的时间(例如录制内容)

我的测试代码返回10 ms的每个值:

public static void Main() { var dt1 = DateTime.Now; System.Threading.Thread.Sleep(2); var dt2 = DateTime.Now; // On my machine the values will be at least 10 ms apart Console.WriteLine("First: {0}, Second: {1}", dt1.Millisecond, dt2.Millisecond); } 

是否有另一种解决方案可以获得精确的当前时间到毫秒?

有人建议看秒表类。 虽然秒表类非常准确,但它并没有告诉我当前时间,我需要一些东西来保存我的程序状态。

奇怪的是,你的代码在我的Win7下的四核上运行得非常好,几乎每次都产生相距2毫秒的值。

所以我做了一个更全面的测试。 这是Thread.Sleep(1)示例输出。 代码打印循环中对DateTime.UtcNow连续调用之间的ms数:

睡1

每行包含100个字符,因此在“干净运行”中表示100毫秒的时间。 所以这个屏幕大约需要2秒钟。 最长的抢占时间是4毫秒; 此外,当每次迭代花费1毫秒时,有一个持续时间约为1秒的周期。 这几乎是实时操作系统的质量! 1 🙂

所以我再次尝试使用Thread.Sleep(2)

睡觉2

再次,几乎完美的结果。 这一次每行长度为200毫秒,并且有一个差不多3秒钟的运行,其中差距从来没有超过2毫秒。

当然,接下来要看的是我的机器上DateTime.UtcNow的实际分辨率。 这是一场完全没有睡觉的跑步; a . 如果UtcNow根本没有改变则打印出来:

没睡觉

最后,在调查一个奇怪的时间戳相差15ms的情况下,在产生上述结果的同一台机器上,我遇到了以下奇怪的事件:

在此处输入图像描述在此处输入图像描述

Windows API中有一个名为timeBeginPeriod的函数,应用程序可以使用它暂时增加计时器频率,因此这可能就是这里发生的事情。 定时器分辨率的详细文档可通过硬件开发中心存档获得 ,特别是Timer-Resolution.docx (一个Word文件)。

结论:

  • DateTime.UtcNow 可以具有比15ms更高的分辨率
  • Thread.Sleep(1) 可以睡1ms
  • 在我的机器上, UtcNow增长1ms(给出或采取舍入误差 – Reflector显示在UtcNow中有一个除法)。
  • 当所有内容都是基于15.6ms的时候,进程可以切换到低分辨率模式,而高速分辨率模式,有1ms分片,可以在运行时进行切换。

这是代码:

 static void Main(string[] args) { Console.BufferWidth = Console.WindowWidth = 100; Console.WindowHeight = 20; long lastticks = 0; while (true) { long diff = DateTime.UtcNow.Ticks - lastticks; if (diff == 0) Console.Write("."); else switch (diff) { case 10000: case 10001: case 10002: Console.ForegroundColor=ConsoleColor.Red; Console.Write("1"); break; case 20000: case 20001: case 20002: Console.ForegroundColor=ConsoleColor.Green; Console.Write("2"); break; case 30000: case 30001: case 30002: Console.ForegroundColor=ConsoleColor.Yellow; Console.Write("3"); break; default: Console.Write("[{0:0.###}]", diff / 10000.0); break; } Console.ForegroundColor = ConsoleColor.Gray; lastticks += diff; } } 

事实certificate,存在一个可以改变计时器分辨率的无证function。 我没有调查过细节,但我想我会在这里发布一个链接: NtSetTimerResolution

1 当然,我更加确定操作系统尽可能空闲,并且有四个相当强大的CPU内核可供使用。 如果我将所有四个核心加载到100%,则图像会完全改变,并且在任何地方都会有长时间的抢占。

处理毫秒时DateTime的问题不是由于DateTime类,而是与CPU标记和线程切片有关。 本质上,当调度程序暂停某个操作以允许其他线程执行时,它必须在恢复之前至少等待1个时间片,这在现代Windows操作系统上大约需要15ms。 因此,任何暂停低于15ms精度的尝试都会导致意外结果。

如果你在做任何事情之前拍摄当前时间的快照,你可以将秒表添加到你存储的时间,不是吗?

您应该问自己是否确实需要准确的时间,或者只是关闭足够的时间加上一个增加的整数。

你可以通过在等待事件(例如互斥,选择,轮询,等待*等)之后立即获取(),然后在其中添加序列号(可能在纳秒范围内或有空间的任何地方)来做好事。

您还可以使用rdtsc机器指令(某些库为此提供API包装,不确定是否在C#或Java中执行此操作)以从CPU获得便宜的时间并将其与现在()的时间相结合。 rdtsc的问题在于,在具有速度扩展的系统上,您永远无法确定它将要做什么。 它也很快包裹起来。

我用来完成100%准确完成此任务的所有内容都是计时器控件和标签。

代码不需要太多解释,相当简单。 全局变量:

 int timer = 0; 

这是tick事件:

 private void timeOfDay_Tick(object sender, EventArgs e) { timeOfDay.Enabled = false; timer++; if (timer <= 1) { timeOfDay.Interval = 1000; timeOfDay.Enabled = true; lblTime.Text = "Time: " + DateTime.Now.ToString("h:mm:ss tt"); timer = 0; } } 

这是表单加载:

 private void DriverAssignment_Load(object sender, EventArgs e) { timeOfDay.Interval= 1; timeOfDay.Enabled = true; } 

回答关于更精确API的问题的第二部分,AnotherUser的评论引导我找到这个解决方案,在我的场景中克服了DateTime.Now精度问题:

 static FileTime time; public static DateTime Now() { GetSystemTimePreciseAsFileTime(out time); var newTime = (ulong)time.dwHighDateTime << (8 * 4) | time.dwLowDateTime; var newTimeSigned = Convert.ToInt64(newTime); return new DateTime(newTimeSigned).AddYears(1600).ToLocalTime(); } public struct FileTime { public uint dwLowDateTime; public uint dwHighDateTime; } [DllImport("Kernel32.dll")] public static extern void GetSystemTimePreciseAsFileTime(out FileTime lpSystemTimeAsFileTime); 

在我自己的基准测试中,迭代1M,它返回平均3个滴答与DateTime.Now 2滴答。

为什么1600不在我的管辖范围内,但我用它来获得正确的年份。

编辑:这仍然是win10的一个问题。 任何感兴趣的人都可以运行这种证据:

 void Main() { for (int i = 0; i < 100; i++) { Console.WriteLine(Now().ToString("yyyy-MM-dd HH:mm:ss.fffffff")); Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff")); Console.WriteLine(); } } // include the code above 

您可以使用DateTime.Now.Ticks,阅读MSDN上的artical

“单个刻度表示一百纳秒或一千万分之一秒。”