29

我有代码在循环中运行,它根据当前时间保存状态。有时这可能只是相隔几毫秒,但出于某种原因,DateTime.Now 似乎总是会返回至少相隔 10 毫秒的值,即使它只是在 2 或 3 毫秒之后。这提出了一个主要问题,因为我正在保存的状态取决于它被保存的时间(例如记录一些东西)

我的测试代码以 10 毫秒的间隔返回每个值:

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);
}

关于如何将当前时间精确到毫秒的另一种解决方案?

有人建议看一下秒表类。尽管 Stopwatch 类非常准确,但它并不能告诉我当前时间,这是我保存程序状态所需要的。

4

7 回答 7

43

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

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

睡觉 1

每行包含 100 个字符,因此代表“干净运行”的 100 毫秒时间。所以这个屏幕大约覆盖了 2 秒。最长抢占4ms;此外,有一段时间持续大约 1 秒,而每次迭代恰好需要 1 毫秒。这几乎是实时的操作系统质量!1 :)

所以我又试了一次,Thread.Sleep(2)这次:

睡觉 2

再次,几乎完美的结果。这次每行有 200 毫秒长,并且有一个运行将近 3 秒的时间,其中的间隔只有 2 毫秒。

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

不睡觉

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

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

Windows API 中有一个叫做 的函数timeBeginPeriod,应用程序可以使用它来临时增加定时器频率,所以这大概就是这里发生的事情。计时器分辨率的详细文档可通过Hardware Dev Center Archive获得,特别是Timer-Resolution.docx(一个 Word 文件)。

结论:

  • DateTime.UtcNow 可以有比 15ms 高得多的分辨率
  • Thread.Sleep(1) 可以睡正好 1ms
  • 在我的机器上,UtcNow一次增长正好 1 毫秒(给出或取舍入误差 - 反射器显示 中有一个除法UtcNow)。
  • 当一切都基于 15.6 毫秒时,该过程可以切换到低分辨率模式,以及动态切换到具有 1 毫秒切片的高分辨率模式。

这是代码:

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;
    }
}

事实证明,存在一个可以改变计时器分辨率的未记录函数。我没有调查细节,但我想我会在这里发布一个链接:NtSetTimerResolution.

1当然,我特别确定操作系统尽可能空闲,并且有四个相当强大的 CPU 内核可供使用。如果我将所有四个内核都加载到 100%,那么情况就会完全改变,到处都是长时间的抢占。

于 2011-02-10T21:23:50.900 回答
10

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

于 2008-11-21T02:22:32.433 回答
2

如果您在执行任何操作之前对当前时间进行快照,您可以将秒表添加到您存储的时间,不是吗?

于 2008-11-21T01:58:43.450 回答
1

你应该问自己是否真的需要准确的时间,或者只是足够接近的时间加上一个递增的整数。

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

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

于 2008-11-21T01:59:49.780 回答
0

我用来 100% 准确完成这项任务的只是一个计时器控件和一个标签。

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

int timer = 0;

这是滴答事件:

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;


}
于 2015-03-05T00:41:47.033 回答
0

在回答您关于更精确 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
于 2016-02-05T13:57:00.097 回答
-4

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

“一个滴答声代表一百纳秒或百万分之一秒。”

于 2008-11-21T01:57:50.363 回答