C# TimeSpan 秒和毫秒不同步

C# TimeSpan seconds and milliseconds are out of sync

我正在更新我们的团队维护的数据包模拟软件,并使用它来测试我们正在处理的其他应用程序。这些模拟数据包包含的两个字段是 unix 时间(秒)和纳秒。这是自 1970 年 1 月 1 日以来经过的时间(以秒和纳秒为单位)。我们正在为这个特定的应用程序使用 c#,并使用 TimeSpan 对象获取经过的时间。

TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

packet.SetTimeSeconds(Convert.ToUInt64(ts.Totalseconds));

packet.SetTimeNanoseconds(Convert.ToUInt64(1e6 * ts.Milliseconds));

当数据包在其目的地被捕获并显示内容时,秒值似乎滞后于纳秒值相当大的量。纳秒值将上升到阈值、滚动并再次开始上升,但秒值需要几个数据包才能滚动。我已经检查过在代码中的其他地方设置后秒值是否以某种方式更新,但事实并非如此。剩余的数据包值在上面的代码片段之后设置,然后使用套接字将数据包发送到目的地。

我知道一个 tick 发生的时间是 16 毫秒,更新可能在两个数据包之间进行。这可能会导致它们暂时不同步。在那种情况下,由于滴答率,我预计它们最多不会匹配一个数据包。在我的情况下,在每 100 毫秒生成数据时更新秒值之前,这种不匹配发生在 3 或 4 个数据包上。

重要的是要注意,由于工作环境,我在法律上不能将完整的源代码放在网上,所以不要问。在数据包中设置时间后,我还检查了所有逻辑,以确保没有任何内容被覆盖。我的主要问题是这是否只是 DateTime.UtcNow 的问题,或者可能通过 TimeSpan 获得准确的经过时间。这不是成败问题,因为这只是一个模拟器,但如果我们的测试数据包中有准确的模拟时间就好了。

编辑:

关于补充信息,即使我不使用我的数据包结构并将值发送给接收方,也会遇到此问题。我已经编写了以下循环来测试 TimeSpan/DateTime 值,而不会受到其他逻辑的影响。

for (int i = 0; i < 150; i++)
{

     TimeSpan temp = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

     Console.WriteLine("The seconds elapsed is: " + Convert.ToUInt64(temp.TotalSeconds) + " The nanoseconds portion: " + Convert.ToUInt64(1e6 * temp.Milliseconds);

}

请记住,以下循环将打印出多个相同值的实例,因为处理器可以在时钟滴答之间多次循环循环(每个滴答需要 16 毫秒)。如果您 运行 循环,您将看到纳秒值按预期上升到 999000000,然后在达到新的秒数时翻转以重新开始。当您看到翻转时,请检查秒值,您会注意到尚未更新。我会 post 打印输出,但我的联网机器没有 IDE。

解决方案:

正如下面已接受的答案所指出的,我正在使用 convert.ToUInt64 将 TotalSeconds 值(双精度)转换为 Ulong。这样做是为了满足我们内部构建的库功能。此强制转换使秒值看起来不准确。我最终使用 Math.Floor(double) 函数删除了转换前几秒的分形部分,一切都保持同步。下面列出了我更新的代码。

TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

packet.SetTimeSeconds(Convert.ToUInt64(Math.Floor(ts.Totalseconds)));

packet.SetTimeNanoseconds(Convert.ToUInt64(1e6 * ts.Milliseconds));

我没有看到任何不一致之处。下面是我从你的代码中得到的一个 TimeSpan 对象。

TimeSpan.TicksPerMillisecond    : 10,000
TimeSpan.TicksPerDay            : 864,000,000,000

Ticks               : 15511159080437769         1551115908043.7769  milliseconds

Days                : 17952                     1551052800000       milliseconds
Hours               : 17                             61200000       milliseconds
Minutes             : 31                              1860000       milliseconds
Seconds             : 48                                48000       milliseconds
Milliseconds        : 43                                   43       milliseconds
Sum                 :                           1551115908043       milliseconds (NOTE: we don't have microsecond or nanosecond value bearing properties)

TotalDays           : 17952.730417173341        15511159080437769.0 / TimeSpan.TicksPerDay
TotalHours          : 430865.53001216019        15511159080437769.0 / TimeSpan.TicksPerHour
TotalMinutes        : 25851931.800729614        15511159080437769.0 / TimeSpan.TicksPerMinute
TotalSeconds        : 1551115908.0437768        15511159080437769.0 / TimeSpan.TicksPerSecond
TotalMilliseconds   : 1551115908043.7769        15511159080437769.0 / TimeSpan.TicksPerMillisecond

如您所见,所有的值都是根据TimeSpan.Ticks级别计算的。你可以计算

更新:

使用以下代码获取纳秒部分或纳秒总时间跨度的正确值

public static class TimeSpanExtension {
    const decimal TicksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000000m;
    public static decimal GetTotalNanoSeconds(this TimeSpan ts) => ts.Ticks / TicksPerNanosecond;
    public static decimal GetMicroAndNanoSeconds(this TimeSpan ts) => ts.Ticks % TimeSpan.TicksPerMillisecond / TicksPerNanosecond;
}

更新 2:

从问题中的代码更新中,我注意到您正在使用 Convert.ToUInt64 来四舍五入秒数,这让您感到困惑。

Console.WriteLine("The seconds elapsed is: " + Convert.ToUInt64(temp.TotalSeconds) +
                    " The nanoseconds portion: " + Convert.ToUInt64(1e6 * temp.Milliseconds));

你应该使用下面的代码来代替上面的代码,看看有没有不一致。您可以看到 TotalSecondsMilliseconds 一起滚动,但 Convert.ToUInt64(temp.TotalSeconds) 不会滚动

// C# 6.0 string interpolation syntax
Console.WriteLine($"{temp.TotalSeconds}, {Convert.ToUInt64(temp.TotalSeconds)}, {temp.Milliseconds,000}");

// similar statement without string interpolation
Console.WriteLine(temp.TotalSeconds + ", " + Convert.ToUInt64(temp.TotalSeconds) + ", " + temp.Milliseconds);