Как часто обновляется DateTime.Now? или есть более точный API, чтобы получить текущее время?

У меня есть код, запущенный в цикле, и он сохраняет состояние на основе текущего времени. Иногда это может быть всего лишь в миллисекундах, но по какой-то причине кажется, что 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);
}

Есть ли еще одно решение о том, как получить точное текущее время до миллисекунды?

Кто-то предложил посмотреть класс Секундомера. Хотя класс секундомера очень точен, он не говорит мне о текущем времени, что мне нужно, чтобы сохранить состояние моей программы.

Ответ 1

Любопытно, что ваш код отлично работает на моем четырехъядерном ядре под Win7, генерируя значения ровно 2 мс друг от друга почти каждый раз.

Итак, я сделал более тщательный тест. Здесь мой пример выводится для Thread.Sleep(1). Код печатает количество мс между последовательными вызовами в DateTime.UtcNow в цикле:

sleep 1

Каждая строка содержит 100 символов и, таким образом, составляет 100 мс времени при "чистом прогоне". Таким образом, этот экран занимает примерно 2 секунды. Самое длинное предположение - 4 мс; Более того, период длился около 1 секунды, когда каждая итерация составляла ровно 1 мс. Это почти оперативное качество ОС! 1:)

Итак, я попробовал еще раз: Thread.Sleep(2) на этот раз:

sleep 2

Опять же, почти идеальные результаты. На этот раз каждая строка длится 200 мс, и там работает почти 3 секунды, где разрыв никогда не был чем-то иным, чем ровным 2 мс.

Естественно, следующее, что нужно увидеть, - это фактическое разрешение DateTime.UtcNow на моей машине. Здесь бег без сна вообще; a . печатается, если UtcNow не изменилось вообще:

no sleep

Наконец, при изучении странного случая временных меток на расстоянии 15 мс на той же машине, которая произвела вышеупомянутые результаты, я столкнулся с следующими любопытными событиями:

enter image description hereenter image description here

В Windows API есть функция, называемая timeBeginPeriod, какие приложения могут использовать для временного увеличения частоты таймера, поэтому это предположительно что здесь случилось. Подробную документацию по разрешению таймера можно получить через Hardware Dev Center Archive, в частности Timer-Resolution.docx (файл Word).

Выводы:

  • DateTime.UtcNow может иметь гораздо более высокое разрешение, чем 15 мс
  • Thread.Sleep(1) может спать ровно 1 мс
  • На моей машине UtcNow растет на ровно 1 мс за раз (дайте или возьмите ошибку округления - Reflector показывает, что существует деление в 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 Конечно, я убедился, что ОС как можно меньше, и в его распоряжении есть четыре довольно мощных ядра процессора. Если я загружу все четыре ядра на 100%, изображение полностью изменится с длинными превенциями во всем мире.

Ответ 2

Проблема с DateTime при работе с миллисекундами вообще не связана с классом DateTime, а скорее связана с тиками ЦП и срезами потоков. По сути, когда операция приостанавливается планировщиком, чтобы разрешить выполнение других потоков, она должна подождать как минимум 1 временной отрезок перед возобновлением, который составляет около 15 мс в современных ОС Windows. Поэтому любая попытка сделать паузу менее чем на 15 мс приведет к неожиданным результатам.

Ответ 3

Если вы сделаете мгновенный снимок текущего времени, прежде чем что-либо сделать, вы можете просто добавить секундомер к моменту сохранения, не?

Ответ 4

Вы должны спросить себя, действительно ли вам нужно точное время или просто достаточно близкое время и увеличивающееся целое число.

Вы можете делать хорошие вещи, получая сейчас() сразу после события ожидания, такого как мьютекс, выбор, опрос, WaitFor * и т.д., а затем добавление к нему серийного номера, возможно, в наносекундном диапазоне или там, где есть номер.

Вы также можете использовать командную инструкцию rdtsc (для некоторых библиотек для этого есть обертка API, не уверенная в том, что вы делаете это на С# или Java), чтобы получить дешевое время от процессора и объединить это с течением времени(). Проблема с rdtsc заключается в том, что в системах со скоростным масштабированием вы никогда не сможете быть уверенными, что это будет делать. Он также довольно быстро обертывается.

Ответ 5

Все, что я использовал для выполнения этой задачи на 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;


}

Ответ 6

Отвечая на вторую часть вашего вопроса относительно более точного 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 ticks.

Почему 1600 из моей юрисдикции, но я использую его, чтобы получить правильный год.

EDIT: Это все еще проблема на 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

Ответ 7

Вы можете использовать DateTime.Now.Ticks, прочитать текст на MSDN

"Один тик представляет собой сто наносекунд или одну десятую миллионную долю секунды".