Как часто обновляется 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);
}
Есть ли другое решение о том, как получить точное текущее время с точностью до миллисекунды?
Кто-то предложил взглянуть на класс Секундомер. Хотя класс секундомера очень точен, он не говорит мне о текущем времени, что мне нужно, чтобы сохранить состояние моей программы.
7 ответов
Любопытно, что ваш код прекрасно работает на моем четырехъядерном ядре под Win7, генерируя значения с интервалом в 2 мс почти каждый раз.
Итак, я сделал более тщательный тест. Вот мой пример вывода для Thread.Sleep(1)
, Код выводит количество мс между последовательными вызовами DateTime.UtcNow
в цикле:
Каждая строка содержит 100 символов и, таким образом, представляет 100 мс времени в "чистом цикле". Так что этот экран занимает примерно 2 секунды. Самый длинный приоритет был 4 мс; кроме того, был период продолжительностью около 1 секунды, когда каждая итерация занимала ровно 1 мс. Это практически качество ОС в реальном времени!1:)
Поэтому я попробовал еще раз, с Thread.Sleep(2)
этот раз:
Опять же, почти идеальные результаты. На этот раз длина каждой строки составляет 200 мс, и есть пробег длиной почти 3 секунды, где разрыв никогда не превышал 2 мс.
Естественно, следующее, что нужно увидеть, это фактическое разрешение DateTime.UtcNow
на моей машине. Вот бег без сна вообще; .
печатается, если UtcNow
не изменился вообще:
Наконец, при расследовании странного случая, когда временные метки находились на расстоянии 15 мс на той же машине, которая дала вышеуказанные результаты, я столкнулся со следующими любопытными случаями:
В Windows API есть функция, которая называется timeBeginPeriod
, которые приложения могут использовать для временного увеличения частоты таймера, так что это, вероятно, то, что здесь произошло. Подробная документация по разрешению таймера доступна через архив Центра разработки оборудования, в частности, 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%, картина полностью изменится, с длинными вытеснениями повсюду.
Проблема с DateTime при работе с миллисекундами вовсе не связана с классом DateTime, а скорее связана с тиками процессора и срезами потоков. По сути, когда планировщик приостанавливает операцию, чтобы позволить другим потокам выполняться, он должен ждать как минимум 1 интервал времени, прежде чем возобновить работу, что составляет около 15 мс в современных ОС Windows. Поэтому любая попытка сделать паузу с точностью менее 15 мс приведет к неожиданным результатам.
Если вы делаете снимок текущего времени, прежде чем что-то делать, вы можете просто добавить секундомер к тому времени, которое вы сохранили, не так ли?
Вы должны спросить себя, действительно ли вам нужно точное время или просто достаточно времени плюс увеличивающееся целое число.
Вы можете делать хорошие вещи, получая now() сразу после события ожидания, такого как мьютекс, select, poll, WaitFor* и т. Д., А затем добавляя к нему серийный номер, возможно, в наносекундном диапазоне или там, где есть место.
Вы также можете использовать машинную инструкцию rdtsc (некоторые библиотеки предоставляют обертку API для этого, не будучи уверенным в том, что делать это в C# или Java), чтобы получить дешевое время от ЦП и объединить его со временем (). Проблема с rdtsc заключается в том, что в системах с масштабированием скорости вы никогда не можете быть полностью уверены, что он собирается делать. Это также оборачивается довольно быстро.
Отвечая на вторую часть вашего вопроса о более точном 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
Все, что я использовал для 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;
}
Вы можете использовать DateTime.Now.Ticks, читать статью на MSDN
"Один тик представляет сто наносекунд или одну десятую миллионную секунды".