Как я могу (разумно) точно выполнить действие каждые N миллисекунд?

У меня есть машина, которая использует NTP-клиент для синхронизации до времени в Интернете, поэтому системные часы должны быть достаточно точными.

У меня есть приложение, которое я разрабатываю, которое регистрирует данные в режиме реального времени, обрабатывает их и затем передает их. Теперь я хотел бы выводить эти данные каждые N миллисекунд, выровненные по системным часам. Так, например, если я хочу сделать интервалы 20 мс, мои результаты должны быть примерно такими:

13:15:05:000
13:15:05:020
13:15:05:040
13:15:05:060

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

Любые предложения о том, как добиться этого с разумной точностью (близкой к 1 мс или лучше, было бы неплохо), были бы с благодарностью приняты.

8 ответов

Решение

Не знаю, насколько хорошо он играет с C++/CLR, но вы, вероятно, хотите посмотреть на мультимедийные таймеры,
Windows на самом деле не в реальном времени, но это так близко

Вы можете получить довольно точную временную метку из timeGetTime (), когда уменьшите период времени. Вам просто нужно немного поработать, чтобы преобразовать возвращаемое значение в часы. Этот пример кода C# демонстрирует подход:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        timeBeginPeriod(1);
        uint tick0 = timeGetTime();
        var startDate = DateTime.Now;
        uint tick1 = tick0;
        for (int ix = 0; ix < 20; ++ix) {
            uint tick2 = 0;
            do {  // Burn 20 msec
                tick2 = timeGetTime();
            } while (tick2 - tick1 < 20);
            var currDate = startDate.Add(new TimeSpan((tick2 - tick0) * 10000));
            Console.WriteLine(currDate.ToString("HH:mm:ss:ffff"));
            tick1 = tick2;
        }
        timeEndPeriod(1);
        Console.ReadLine();
    }
    [DllImport("winmm.dll")]
    private static extern int timeBeginPeriod(int period);
    [DllImport("winmm.dll")]
    private static extern int timeEndPeriod(int period);
    [DllImport("winmm.dll")]
    private static extern uint timeGetTime();
}

Если подумать, это просто измерение. Чтобы периодически выполнять действие, вам нужно использовать timeSetEvent (). Пока вы используете timeBeginPeriod (), вы можете получить период обратного вызова довольно близко к 1 мсек. Одним из достоинств является то, что он будет автоматически компенсировать, когда предыдущий обратный вызов по какой-либо причине опоздал

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

Лучше всего использовать встроенную сборку и писать этот фрагмент кода в качестве драйвера устройства.

Сюда:

  • Вы можете контролировать количество команд
  • Ваша заявка будет иметь приоритет выполнения

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

20 мс - это примерно длина временного интервала в Windows. Невозможно надежно использовать временные интервалы в 1 мс в окнах без добавления некоторого RT, такого как Intime. В собственно окнах я думаю, что ваши варианты: WaitForSingleObject, SleepEx и занятый цикл.

Поскольку, как вы сказали, это не обязательно должно быть идеально, есть кое-что, что можно сделать.

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

Как уже указывалось, Windows не является операционной системой реального времени. Таким образом, вы должны предположить, что даже если вы запланируете отключение таймера на ":0010", ваш код может даже не исполниться намного позже этого времени (например, ":0540"). Пока вы правильно справитесь с этими проблемами, все будет хорошо.

CreateWaitableTimer/SetWaitableTimer и поток с высоким приоритетом должны быть с точностью до 1 мс. Я не знаю, почему поле миллисекунды в вашем примере вывода имеет четыре цифры, максимальное значение составляет 999 (так как 1000 мс = 1 секунда).

Другие вопросы по тегам