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

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

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

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

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

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

Ответы [ 8 ]

2 голосов
/ 25 марта 2010

Вы можете получить довольно точную временную метку из 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 мсек. Одним из достоинств является то, что он автоматически компенсирует, когда предыдущий обратный вызов по какой-либо причине опоздал.

2 голосов
/ 25 марта 2010

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

1 голос
/ 25 марта 2010

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

1 голос
/ 25 марта 2010

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

1 голос
/ 25 марта 2010

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

Таким образом:

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

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

0 голосов
/ 25 марта 2010

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

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

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

0 голосов
/ 25 марта 2010

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

...