Подтвердить что ты не робот

Какой самый стабильный источник времени для приложения .NET?

Основная проблема

У меня проблема с метками времени в моем приложении С#. Я получаю данные из удаленного TCP-соединения асинхронно. Каждый раз, когда я получаю данные, я обновляю переменную метки времени до DateTime.Now. В отдельном потоке, один раз в секунду, я проверяю, было ли это больше, чем предопределенный период таймаута с момента моего последнего приема, и если это так, отключите. Этот метод работает уже много лет, но теперь у меня есть ситуация, когда приложение устанавливается на машине с неустойчивым источником времени. Каждые несколько дней машинное время "автоматически корректирует", и я преждевременно отказываюсь от подключения. Код в основном выглядит следующим образом:

Процесс получения

void OnReceiveComplete(IAsyncResult ar) {
    ...
    mySocket.EndReceive(ar);
    lastRxTime = DateTime.Now;
    ...
}

Проверить процесс

void CheckConnection() {
    TimeSpan ts = DateTime.Now.Subtract(lastRxTime);
    if(ts.TotalMilliseconds > timeout) {
        Disconnect(string.Format("No packet received from the server for over {0} seconds.", timeout / 1000));
    }
}

У меня есть достоверные записи Wireshark во время проблемы, и прямо перед отключением я вижу NTP-трафик, который заканчивается тем, что выглядит как исправление не менее 1 минуты. Это, очевидно, приводит к сбою процесса проверки.

Технические детали/ответы на ожидаемые вопросы

  • У меня есть контроль над обоими концами соединения, но не между физическим уровнем между ними (часто спутниковая связь низкого качества). Вот почему эта проверка таймаута находится на месте.
  • Поскольку данные являются асинхронными, есть условие для отправки небольшого биения, если данные не были отправлены с сервера в течение периода времени, равного половине таймаута.
  • Может быть несколько экземпляров этого процесса (например, при подключении к нескольким серверам).
  • Все коммуникации используют асинхронные методы (которые предположительно используют порты завершения).
  • Процесс проверки запускается в отдельном потоке, который используется всеми клиентами на одной машине.

Завершенные исследования (т.е. возможные решения)

Мои поисковые запросы Google в этом месте привели к следующему:

  • Я понимаю, что для повышения производительности я использовал DateTime.UtcNow вместо DateTime.Now. Это не повлияет на сам вопрос.
  • Реализация, зависящая от Ticks, будет лучшим решением.
  • Есть два варианта получения тиков - Environment.TickCount и Stopwatch.GetTimestamp()
  • Согласно моим исследованиям, Environment.TickCount может быть восприимчив к корректировкам времени, но я не уверен, при каких обстоятельствах. Кроме того, поскольку я использую эту же методологию в других условиях более высокой производительности, разрешение 10-16 мсек может быть проблемой (хотя и не в конкретном случае, который я представляю здесь).
  • Stopwatch.GetTimestamp() может вернуться к DateTime.Now.Ticks, когда часы высокой производительности недоступны. Я не уверен, как часто это произойдет (какие-либо машины НЕ поставляются с часами с высокой производительностью), но я уверен, что если он прибегнет к Ticks, будет возникать одна и та же проблема.
  • Я также прочитал, что Stopwatch.GetTimestamp() будет использовать вызов API QueryPerformanceCounter(), и это может быть нестабильным при вызове из нескольких потоков.

Окончательный вопрос

Мне любопытно, какой лучший способ генерировать метку времени lastRxTime будет? Я слишком беспокоюсь о проблемах с низким правдоподобием в функциях Environment.TickCount и Stopwatch.GetTimestamp()? Я открыт для альтернативных реализаций, если они учитывают многопоточный характер приложения, а также проблемы с качеством ссылок.

ОБНОВЛЕНИЕ 7/17/2013 (Развернуто решение!)

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

Во-первых, проблема NTP на самом деле была симптомом другой проблемы. В сети, которая обнаруживает проблему, есть домен AD с двумя серверами, на которых работает мой код, настроенный как контроллеры домена. Оказывается, DC - это источники времени для домена. Также выясняется, что системное время сбрасывается с часов реального времени в этих системах на минуту примерно на 11 дней, после чего Windows исправляет проскальзывание. Как только он исправляет проскальзывание на первом DC, второй DC синхронизирует свое время и оба сталкиваются с проблемой, описанной выше.

Основываясь на обратной связи и моем первоначальном исследовании, я создал тестовую программу для запуска во время отключения для регистрации значений для DateTime.Now, Environment.TickCount и Stopwatch.GetTimestamp(). Я обнаружил, что во время коррекции ни Environment.TickCount, ни StopWatch.GetTimeStamp() вообще не соскользнули, а это значит, что они были хорошими кандидатами для замены в DateTime.Now(). Я пошел с TickCount, потому что он гарантированно будет на всех моих развернутых серверах (тогда как секундомер может вернуться к объекту DateTime на некоторых машинах, которые мне еще предстоит найти). Он работает до сих пор без проблем. Я сделал должную уловку в вопросе о переходе, чтобы предотвратить возникновение этой формы, но мне нужно будет дождаться, когда моя система будет так долго.

Я хочу отметить, что если кто-либо другой сталкивается с подобными проблемами, они не должны отказываться от использования каких-либо других представленных решений в приведенном ниже списке. Каждый из них имеет свой собственный merrit, на самом деле простой счетчик, вероятно, является лучшим решением для большинства обстоятельств. Причина, по которой я не пошел на это решение, заключалась в том, что у меня есть аналогичный код в отдельной области, который сильно зависит от жестких таймингов. я cna обрабатывает разрешение в размере 16 мсек или около того, но не может справиться с тем временем, с которым сталкиваются встречные решения (я использовал код, подобный этому, в отдельном продукте, который заканчивался дрейфом более чем через секунду в час и приносил мне из спецификации для проекта).

Опять же, спасибо всем, и если еще не придет, я обязательно обновлю вопрос.

4b9b3361

Ответ 1

Я слишком беспокоюсь о проблемах с низким правдоподобием в функциях Environment.TickCount и Stopwatch.GetTimestamp()?

Потенциально, да.

Я бы предпочел Stopwatch.GetTimestamp(). Это будет использовать высокопроизводительные таймеры, если они доступны в вашей системе, и отмените DateTime.Ticks, если это не вариант. Таким образом, вы получите наилучшее время, когда это возможно на вашем оборудовании, и хороший вариант в качестве резервного, когда таймеры с высокой производительностью недоступны.

Хотя Environment.TickCount может быть интересным вариантом, вам нужно будет обработать случай, когда это переполнение, если возможно, что ваша машина будет работает более 24,9 дней.

Ответ 2

Я не вижу проблем с использованием Environment.TickCount. В документации указано, что она возвращает:

32-разрядное целое число со знаком, содержащее время в миллисекундах, прошедшее с момента последнего запуска компьютера.

Далее говорится:

Значение этого свойства выводится из системного таймера и сохраняется как 32-разрядное целое число со знаком.

Я не вижу, как коррекция времени NTP может повлиять на это. У вас есть ссылка, которая предполагает иное? Трудная часть касается обертывания. Таким образом, при сравнении текущего количества меток с предыдущим, если вы получите отрицательное число, вы узнаете, что происходит обход.

Лично я считаю, что это превосходный механизм для использования, потому что возвращаемое значение находится в миллисекундах, поэтому коэффициент преобразования для вашего тайм-аута будет простым. В противном случае с помощью Stopwatch.GetTimestamp вам потребуется выполнить дополнительную работу, чтобы повлиять на частоту тика.

Кроме того, я предполагаю, что это реализовано внутренне с вызовом функции Win32 GetTickCount. (dotPeek показывает, что он отмечен MethodImplOptions.InternalCall, поэтому я не могу точно сказать). Но в документации для этого указано:

Примечания

Разрешение функции GetTickCount ограничено разрешение системного таймера, которое обычно находится в диапазоне 10 миллисекунды до 16 миллисекунд. Разрешение GetTickCount на функции не влияют корректировки, сделанные GetSystemTimeAdjustment.


Во-вторых, поскольку вы периодически проверяете только каждую секунду, разрешение таймера должно быть < 1 сек. Нет смысла пытаться сделать что-то лучше, поэтому зачем вообще использовать любой вызов API?

private int m_conTimer;

void OneSecondThreadCallback() {
    if (++m_conTimer >= TIMEOUT_VALUE)
        // Connection timed out. React accordingly.
}

Ответ 3

Есть несколько веских причин не использовать DateTime.Now для этой цели

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

  • Так как это "сейчас" локального часового пояса, он не подходит для сравнения или расчета. Это связано с тем, что во многих часовых поясах есть разрывы два раза в год, когда происходят переход на летнее время. Если вы сравниваете свое время с последним событием, тогда, когда часы перевернутся, у вас будет дополнительный час. Когда они откатываются назад, вы можете уйти от 0 до 60 минут (в зависимости от того, когда вы сделали сравнение).

  • Он имеет гораздо меньшую точность, чем Stopwatch или Environment.TickCount. В то время как DateTime способен отображать очень малые промежутки времени, сами системные часы являются точными только примерно от 10 до 30 мс. (Но, возможно, это не так важно для вашего случая использования.)

Использование DateTime.UtcNow делает обращение к первым двум точкам, но не к третьему. Для этого вам нужно полностью держаться подальше от DateTime.

Также есть понятие "дрейф часов". Часы могут замедляться или ускоряться в течение продолжительных периодов времени. Если ОС имеет источник синхронизации времени, она может фактически настроить количество времени, которое фактически проходит в тике, чтобы компенсировать. Вы можете прочитать больше в в этой статье (см. Раздел "Регулировки времени" ).

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

ОДНАКО - ваш вариант использования, вероятно, лучше всего использовать ни одним из этих методов. Вы сказали, что хотите отменить мероприятие после таймаута. Вы также сказали, что у вас есть отдельный поток, который периодически проверяет, прошло ли время ожидания. Все это не актуально. Вместо этого просто используйте класс Timer.

Будьте осторожны, есть три разных таймера, доступных в .Net. Вероятно, вам нужен System.Threading.Timer:

// start the timer, callback in 10000 milliseconds, and don't fire more than once
var timer = new Timer(TimerCallback, null, 10000, Timeout.Infinite);

// to reset the timer when you receive data
timer.Change(10000, Timeout.Infinite);

// to stop the timer completely
timer.Change(Timeout.Infinite, Timeout.Infinite);

// and in your callback
private void TimerCallback(object state)
{
    // disconnect, or do whatever you want
}

Если возможно, вы сбросите таймер из нескольких потоков, вместо этого вы должны использовать System.Timers.Timer - который является потокобезопасным.

// to set up the timer
var timer = new Timer(10000) {AutoReset = false};
timer.Elapsed += TimerOnElapsed;

// to start the timer running
timer.Start();

// to reset the timer
timer.Stop();
timer.Start();

// and the callback
private void TimerOnElapsed(object sender, ElapsedEventArgs args)
{
    // disconnect, or do whatever you want
}