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

StackOverflow https://stackoverflow.com/questions/2517742

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

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

Сюда:

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top