【问题标题】:How to get precise DateTime in C#如何在 C# 中获取精确的日期时间
【发布时间】:2013-08-17 22:55:42
【问题描述】:

我知道DateTime.UtcNow 的精度不高(10-15 毫秒分辨率)。

这对我来说是个问题,因为在我的日志文件中,我想要更好的解决方案来解决竞争条件。

问题是:如何获得比DateTime.UtcNow 提供的更精确的当前日期/时间?

到目前为止,我看到的唯一解决方案是:https://stackoverflow.com/a/15008836/270348。这是解决问题的合适方法吗?

【问题讨论】:

  • 10 毫秒不够准确?
  • @Cole"Cole9"Johnson the OP 提到“我想要一个更好的解决方案来解决竞争条件”
  • 使用 timeBeginPeriod() 不能超过 1 毫秒,对于竞争条件来说还不够好。请注意,仅添加日志记录已经对比赛产生了很大影响。你可以让秒表工作。
  • 你需要阅读这个:Precision and accuracy of DateTime,Eric Lippert blogs.msdn.com/b/ericlippert/archive/2010/04/08/…
  • @RobSiklos 您不想使用该链接解决方案。每次重置时,您的时间都可以向任一方向跳跃 10 毫秒。这将完全破坏您确定记录调用之间的相对时间的能力。您只需要一个永不重置的Stopwatch

标签: c# datetime


【解决方案1】:

您可以使用Stopwatch 更精确地测量时间。然后,您可以让每个日志条目记录从第一个操作开始的时间。如果它很重要,您可以记录第一个操作的DateTime,从而计算其余的时间,但听起来就像从第一个操作开始以来只有滴答声/纳秒就足以满足您的目的。

【讨论】:

  • 值得注意的是秒表相对于挂钟时间漂移,因此在启动时采用 DateTime.UtcNow 然后添加秒表派生的偏移量只会产生相对于“正确”的时间戳彼此在同一个系统中,而不是跨系统。随着时间的推移,准确度会很差(互联网似乎认为每秒高达 0.2 毫秒的漂移并不少见)。
【解决方案2】:

如果您想添加跟踪,您可以编写自己的 ETW 跟踪提供程序,例如 here。然后你不需要关心如何获取时间。 Windows 内核将确保时间准确且事件顺序正确。

如果您不想编写自己的 ETW 提供程序,您可以使用作为 Nuget 包提供的 EventSource,它也允许您从 .NET 4.0 使用它。更多信息在他的博客上有Vance Morrison

使用 ETW 事件时,您可以通过 Windows 性能工具包免费获得功能强大的跟踪分析器。作为额外的好处,您可以在所有进程中进行系统范围的分析,并且影响最小。

这允许您有选择地为您编写的每个事件添加调用堆栈,这在您想要追踪谁确实调用了您的 Dispose 方法两次时非常有用。

【讨论】:

    【解决方案3】:

    你真的需要一个精确的计时器,还是你真正需要的是能提供排序的东西?例如:

    static class AutoIncrement
    {
        private static long num;
    
        public static long Current
        {
            get
            {
                return Interlocked.Increment(ref num);
            }
        }
    }
    
    Console.WriteLine(AutoIncrement.Current);
    Console.WriteLine(AutoIncrement.Current);
    Console.WriteLine(AutoIncrement.Current);
    Console.WriteLine(AutoIncrement.Current);
    Console.WriteLine(AutoIncrement.Current);
    

    这保证是唯一的,并且每次都改变,并且被排序。它的精度优于微秒。它的精度在单台 PC 上绝对

    或者如果你真的想要时间戳......

    static class AutoIncrement
    {
        private static long lastDate;
    
        public static DateTime Current
        {
            get
            {
                long lastDateOrig, lastDateNew;
    
                do
                {
                    lastDateOrig = lastDate;
    
                    lastDateNew = lastDateOrig + 1;
    
                    lastDateNew = Math.Max(DateTime.UtcNow.Ticks, lastDateNew);
                }
                while (Interlocked.CompareExchange(ref lastDate, lastDateNew, lastDateOrig) != lastDateOrig);
    
                return new DateTime(lastDateNew, DateTimeKind.Utc);
            }
        }
    }
    
    DateTime ac = AutoIncrement.Current;
    Console.WriteLine(CultureInfo.InvariantCulture, "{0} {1:yyyy/MM/dd HH:mm:ss.fffffff}", ac.Ticks, ac);
    

    最后一个变体使用DateTime.UtcNow 作为基础,但每次调用如果它没有改变,它就会加一。

    【讨论】:

    • 否 - 我想在我的日志文件中添加更精确的事件时间戳。
    • @RobSiklos 这样的事情?每次通话都保证在前一次通话中至少给出 +1 Tick。精度仍然是 10 毫秒,但您可以区分调用。
    • 我不确定这是否适用于单台 PC。您要记录的行在线程 1 中运行,线程 2 被切换到,记录的行在那里运行,线程 2 检索一个数字,线程 1 被切换回,它检索一个数字-> 竞争条件(如果您在运行该行之前记录, 会发生类似的问题)。 Interlocked 唯一能保证的是,没有两个条目具有相同的编号,但不能保证它们不会以错误的顺序获得编号。因此,您需要使用锁定来确保一个线程运行该行并检索一个数字,而另一个线程无法在其间执行此操作。
    • @svinja 在这种情况下,唯一的解决方案是记录器锁定计时器并在完成写入后释放它。每个其他解决方案都会有同样的问题。即便如此,您将拥有的是日志记录的顺序,而不是操作的顺序。
    • 你需要在运行你想记录的代码行之前锁定。锁定 -> 运行代码行 -> 检索数字 -> 解锁。有了这个,我想你总是有正确的顺序。不管你是在锁内还是锁外进行实际写入,你只需要检索锁内的数字即可。
    猜你喜欢
    • 2022-09-26
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多