我在循环中运行代码,并根据当前时间保存状态。有时这可能只相差几毫秒,但由于某些原因,似乎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);
}
是否有另一种解决方案可以获得精确的当前时间到毫秒?
有人建议查看秒表课程。虽然秒表课程非常准确,但它并没有告诉我当前的时间,这是我需要的东西,以便保存我的程序状态。
答案 0 :(得分:41)
奇怪的是,你的代码在我的Win7下的四核上运行得非常好,几乎每次都会产生相距2毫秒的值。
所以我做了一个更全面的测试。这是Thread.Sleep(1)
的示例输出。代码打印循环中连续调用DateTime.UtcNow
之间的ms数:
每行包含100个字符,因此在“干净运行”中表示100毫秒的时间。所以这个屏幕大约需要2秒钟。最长的抢占时间是4毫秒;此外,当每次迭代花费1毫秒时,有一个持续约1秒的周期。这几乎是实时的操作系统质量! 1 :)
所以这次我再次尝试Thread.Sleep(2)
:
再次,几乎完美的结果。这一次每行的长度为200毫秒,并且运行时间差不多是3秒,其中差距绝对不是2毫秒。
当然,接下来要看的是我机器上DateTime.UtcNow
的实际分辨率。这是一场完全没有睡觉的跑步;如果.
未完全更改,则会打印UtcNow
:
最后,在调查一个奇怪的时间戳相差15ms的情况下,在产生上述结果的同一台机器上,我遇到了以下奇怪的事情:
Windows API中有一个名为timeBeginPeriod
的函数,应用程序可以使用它暂时增加计时器频率,因此这可能就是这里发生的事情。有关计时器分辨率的详细文档可通过Hardware Dev Center Archive获得,特别是Timer-Resolution.docx(Word文件)。
结论:
DateTime.UtcNow
可以的分辨率高于15毫秒Thread.Sleep(1)
可以完全睡眠1毫秒UtcNow
一次增长正好1ms(给出或采取舍入误差 - Reflector显示UtcNow
中有一个除法。)以下是代码:
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%,则图片会完全改变,并且在任何地方都会有长时间的抢占。
答案 1 :(得分:10)
处理毫秒时DateTime的问题根本不是由于DateTime类,而是与CPU标记和线程切片有关。本质上,当调度程序暂停操作以允许其他线程执行时,它必须在恢复之前至少等待1个时间片,这在现代Windows操作系统上大约需要15ms。因此,任何暂停低于15ms精度的尝试都会导致意外结果。
答案 2 :(得分:2)
如果您在做任何事情之前拍摄当前时间的快照,您可以将秒表添加到您存储的时间,不是吗?
答案 3 :(得分:1)
你应该问问自己,你是否真的需要准确的时间,或者只是关闭足够的时间加上一个增加的整数。
你可以通过在等待事件(例如互斥,选择,轮询,等待*等)之后立即获取()然后添加序列号(可能在纳秒范围内或任何位置)来做好事。室。
你也可以使用rdtsc机器指令(有些库为此提供了一个API包装器,不确定在C#或Java中执行此操作)以便从CPU获得便宜的时间并将其与现在的时间()相结合。 rdtsc的问题在于,在具有速度扩展的系统上,您永远无法确定它将要做什么。它也很快包裹起来。
答案 4 :(得分: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;
}
答案 5 :(得分:0)
在回答有关更精确API的问题的第二部分时,AnotherUser的comment引导我找到这个解决方案,在我的场景中克服了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
答案 6 :(得分:-3)
您可以使用DateTime.Now.Ticks,阅读MSDN
上的文章“单个刻度表示一百纳秒或一千万分之一秒。”