Как часто обновляется DateTime.Now? или есть более точный API для получения текущего времени? - PullRequest
25 голосов
/ 21 ноября 2008

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

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

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

Ответы [ 7 ]

41 голосов
/ 11 февраля 2011

Любопытно, что ваш код отлично работает на моем четырехъядерном ядре под 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 на моей машине. Вот бег без сна вообще; . печатается, если UtcNow не изменилось вообще :

no sleep

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

enter image description here enter image description here

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

Выводы:

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

10 голосов
/ 21 ноября 2008

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

2 голосов
/ 21 ноября 2008

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

1 голос
/ 21 ноября 2008

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

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

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

0 голосов
/ 05 февраля 2016

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

Почему 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
0 голосов
/ 05 марта 2015

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


}
0 голосов
/ 21 ноября 2008

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

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

...