C# DateTime.Now 更新的频率如何?或者是否有更精确的 API 来获取当前时间?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/307582/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How frequent is DateTime.Now updated ? or is there a more precise API to get the current time?
提问by
I have code running in a loop and it's saving state based on the current time. Sometimes this can be just milliseconds apart, but for some reason it seems that DateTime.Now will always return values of at least 10 ms apart even if it's only 2 or 3 ms later. This presents a major problem since the state i'm saving depends on the time it was saved (e.g. recording something)
我有一个循环运行的代码,它根据当前时间保存状态。有时这可能仅相隔几毫秒,但出于某种原因,DateTime.Now 似乎总是会返回相隔至少 10 毫秒的值,即使它仅在 2 或 3 毫秒之后。这是一个主要问题,因为我保存的状态取决于保存的时间(例如记录某些内容)
My test code that returns each value 10 ms apart:
我的测试代码返回每个值相隔 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);
}
Is there another solution on how to get the accurate current time up to the millisecond ?
关于如何获得精确到毫秒的当前时间是否有另一种解决方案?
Someone suggested to look at the Stopwatch class. Although the Stopwatch class is very accurate it does not tell me the current time, something i need in order to save the state of my program.
有人建议看秒表类。尽管 Stopwatch 类非常准确,但它不会告诉我当前时间,这是我保存程序状态所需要的。
回答by Rohan West
回答by x0n
IF you take a snap shot of the current time before you do anything, you can just add the stopwatch to the time you stored, no?
如果您在执行任何操作之前拍摄当前时间的快照,您可以将秒表添加到您存储的时间,不是吗?
回答by Zan Lynx
You should ask yourself if you really need accurate time, or just close enough time plus an increasing integer.
你应该问问自己是否真的需要准确的时间,或者只是接近足够的时间加上一个递增的整数。
You can do good things by getting now() just after a wait event such as a mutex, select, poll, WaitFor*, etc, and then adding a serial number to that, perhaps in the nanosecond range or wherever there is room.
您可以通过在等待事件(例如互斥锁、选择、轮询、WaitFor* 等)之后立即获取 now() 来做一些好事,然后向其中添加一个序列号,可能在纳秒范围内或任何有空间的地方。
You can also use the rdtsc machine instruction (some libraries provide an API wrapper for this, not sure about doing this in C# or Java) to get cheap time from the CPU and combine that with time from now(). The problem with rdtsc is that on systems with speed scaling you can never be quite sure what its going to do. It also wraps around fairly quickly.
您还可以使用 rdtsc 机器指令(某些库为此提供了 API 包装器,不确定是否在 C# 或 Java 中执行此操作)从 CPU 获得廉价时间并将其与从 now() 开始的时间相结合。rdtsc 的问题在于,在具有速度扩展的系统上,您永远无法确定它会做什么。它也很快地环绕。
回答by Chris
The problem with DateTime when dealing with milliseconds isn't due to the DateTime class at all, but rather, has to do with CPU ticks and thread slices. Essentially, when an operation is paused by the scheduler to allow other threads to execute, it must wait at a minimum of 1 time slice before resuming which is around 15ms on modern Windows OSes. Therefore, any attempt to pause for less than this 15ms precision will lead to unexpected results.
处理毫秒时 DateTime 的问题根本不是由于 DateTime 类,而是与 CPU 滴答和线程切片有关。本质上,当一个操作被调度程序暂停以允许其他线程执行时,它必须至少等待 1 个时间片才能恢复,这在现代 Windows 操作系统上大约为 15 毫秒。因此,任何暂停小于 15 毫秒精度的尝试都将导致意外结果。
回答by Roman Starkov
Curiously, your code works perfectly fine on my quad core under Win7, generating values exactly 2 ms apart almost every time.
奇怪的是,您的代码在我的 Win7 下的四核上运行得非常好,几乎每次都生成相隔 2 毫秒的值。
So I've done a more thorough test. Here's my example output for Thread.Sleep(1)
. The code prints the number of ms between consecutive calls to DateTime.UtcNow
in a loop:
所以我做了一个更彻底的测试。这是我的示例输出Thread.Sleep(1)
. 代码打印DateTime.UtcNow
循环中连续调用之间的毫秒数:
Each row contains 100 characters, and thus represents 100ms of time on a "clean run". So this screen covers roughly 2 seconds. The longest preemption was 4ms; moreover, there was a period lasting around 1 second when every iteration took exactly 1 ms. That's almost real-time OS quality!1:)
每行包含 100 个字符,因此代表“干净运行”的 100 毫秒时间。所以这个屏幕大约覆盖 2 秒。最长抢占时间为4ms;此外,每次迭代刚好花费 1 毫秒时,有一段持续约 1 秒的时间。这几乎是实时操作系统质量!1:)
So I tried again, with Thread.Sleep(2)
this time:
所以我再次尝试,Thread.Sleep(2)
这次是:
Again, almost perfect results. This time each row is 200ms long, and there's a run almost 3 seconds long where the gap was never anything other than exactly 2ms.
同样,几乎完美的结果。这次每一行的长度为 200 毫秒,并且有一个将近 3 秒的运行时间,其中的间隙只有 2 毫秒。
Naturally, the next thing to see is the actual resolution of DateTime.UtcNow
on my machine. Here's a run with no sleeping at all; a .
is printed if UtcNow
didn't change at all:
当然,接下来要看的是DateTime.UtcNow
我机器上的实际分辨率。这是一次完全不睡觉的跑步;.
如果UtcNow
根本没有改变,则打印a :
Finally, while investigating a strange case of timestamps being 15ms apart on the same machine that produced the above results, I've run into the following curious occurrences:
最后,在调查产生上述结果的同一台机器上时间戳相距 15 毫秒的奇怪情况时,我遇到了以下奇怪的情况:
There is a function in the Windows API called timeBeginPeriod
, which applications can use to temporarily increase the timer frequency, so this is presumably what happened here. Detailed documentation of the timer resolution is available via the Hardware Dev Center Archive, specifically Timer-Resolution.docx(a Word file).
Windows API 中有一个名为 的函数timeBeginPeriod
,应用程序可以使用它来临时增加计时器频率,所以这大概就是这里发生的事情。计时器分辨率的详细文档可通过硬件开发中心存档获得,特别是Timer-Resolution.docx(一个 Word 文件)。
Conclusions:
结论:
DateTime.UtcNow
canhave a much higher resolution than 15msThread.Sleep(1)
cansleep for exactly 1ms- On my machine,
UtcNow
grows grow by exactly 1ms at a time (give or take a rounding error - Reflector shows that there's a division inUtcNow
). - It is possible for the process to switch into a low-res mode, when everything is 15.6ms-based, and a high-res mode, with 1ms slices, on the fly.
DateTime.UtcNow
可以有比 15ms 更高的分辨率Thread.Sleep(1)
可以睡眠正好 1ms- 在我的机器上,
UtcNow
每次增长正好 1 毫秒(给出或采用舍入误差 - 反射器显示 中存在除法UtcNow
)。 - 当所有内容都基于 15.6 毫秒时,该过程可以切换到低分辨率模式,以及具有 1 毫秒切片的高分辨率模式。
Here's the code:
这是代码:
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;
}
}
It turns out there exists an undocumented function which can alter the timer resolution. I haven't investigated the details, but I thought I'd post a link here: NtSetTimerResolution
.
事实证明,存在一个未记录的函数可以改变计时器分辨率。我还没有调查细节,但我想我会在这里发布一个链接:NtSetTimerResolution
。
1Of course I made extra certain that the OS was as idle as possible, and there are four fairly powerful CPU cores at its disposal. If I load all four cores to 100% the picture changes completely, with long preemptions everywhere.
1当然,我特别确定操作系统尽可能处于空闲状态,并且有四个相当强大的 CPU 内核可供其使用。如果我将所有四个内核都加载到 100%,画面就会完全改变,到处都是长时间的抢占。
回答by Hesein Burg
All that I used to accomplish this task 100% accurately is a timer control, and a label.
我用来 100% 准确完成这项任务的只是一个计时器控件和一个标签。
The code does not require much explanation, fairly simple. Global Variables:
代码不需要太多解释,相当简单。全局变量:
int timer = 0;
This is the tick event:
这是滴答事件:
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;
}
}
Here is the form load:
这是表单加载:
private void DriverAssignment_Load(object sender, EventArgs e)
{
timeOfDay.Interval= 1;
timeOfDay.Enabled = true;
}
回答by noontz
Answering the second part of your question regarding a more precise API, the commentfrom AnotherUser lead me to this solution that in my scenario overcomes the DateTime.Now precision issue:
回答关于更精确的 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);
In my own benchmarks, iterating 1M, it returns on an average 3 ticks vs DateTime.Now 2 ticks.
在我自己的基准测试中,迭代 1M,它平均返回 3 个滴答声,而 DateTime.Now 2 个滴答声。
Why 1600 is out of my jurisdiction, but I use it to get the correct year.
为什么 1600 不在我的管辖范围内,但我用它来获得正确的年份。
EDIT: This is still an issue on win10. Anybody interested can run this peace of evidence:
编辑:这仍然是 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