C # Почему частота таймера сильно разряжена?

И System.Timers.Timer, и System.Threading.Timer срабатывают с интервалами, которые значительно отличаются от запрошенных. Например:

new System.Timers.Timer(1000d / 20);

дает таймер, который срабатывает 16 раз в секунду, а не 20.

Чтобы убедиться в отсутствии побочных эффектов от слишком длинных обработчиков событий, я написал эту небольшую тестовую программу:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

Результат выглядит так:

Требуется: 5 Гц; фактическая: 4,8 Гц
запрошенная: 10 Гц; фактическая: 9,1 Гц
запрошенная: 15 Гц; фактическая: 12,7 Гц
запрошенная: 20 Гц; фактическая: 16 Гц
запрошенная: 30 Гц; фактическая: 21,3 Гц
запрошенная: 50 Гц; фактическая: 31,8 Гц
запрошенная: 75 Гц; фактическая: 63,9 Гц
запрошенная: 100 Гц; фактическая: 63,8 Гц
запрошенная: 200 Гц; фактическая: 63,9 Гц
запрошенная: 500 Гц; фактическое: 63,9 Гц

Фактическая частота отклоняется от запрошенной до 36%. (И, очевидно, не может превышать 64 Гц.) Учитывая, что Microsoft рекомендует этот таймер из-за его «большей точности» по сравнению с System.Windows.Forms.Timer, это меня озадачивает.

Кстати, это не случайные отклонения. Каждый раз это одни и те же ценности. И аналогичная тестовая программа для другого класса таймера, System.Threading.Timer, показывает точно такие же результаты.

В моей реальной программе мне нужно собирать измерения со скоростью 50 выборок в секунду. Для этого еще не нужна система реального времени. И очень неприятно получать 32 отсчета в секунду вместо 50.

Любые идеи?

@Chris: Вы правы, все интервалы кажутся целыми кратными примерно 1/64 секунды. Кстати, добавление Thread.Sleep (...) в обработчик событий не имеет никакого значения. Это имеет смысл, учитывая, что System.Threading.Timer использует пул потоков, поэтому каждое событие запускается в свободном потоке.


person Daniel Wolf    schedule 06.01.2009    source источник
comment
Я не уверен, что: для этого еще не нужна система реального времени. может быть правдой, учитывая: мне нужно проводить измерения со скоростью точно 50 выборок в секунду.   -  person JeffH    schedule 29.04.2009
comment
Использование MultiMediaTimer (winmm.dll), представленное Джастином Таннером, отлично работает. Большое спасибо, чувак.   -  person    schedule 29.09.2010
comment
Я согласен. Для меня это звучит как жесткое требование реального времени - именно для решения этой задачи предназначена RTOS. Событие каждые 20 мс с гарантированным ответом 1-2 мс.   -  person TomTom    schedule 29.09.2010
comment
Если вам нужно ровно 50 выборок в секунду, лучше всего использовать внешнюю плату, микроконтроллер или другое оборудование, которое вы контролируете. В Windows происходит МНОГОЕ, и у вас очень ограниченный контроль и видимость проприетарного кода.   -  person Technophile    schedule 13.04.2015


Ответы (10)


Ну, на самом деле я получаю другое число до 100 Гц, с некоторыми большими отклонениями, но в большинстве случаев ближе к запрошенному числу (при использовании XP SP3 с самыми последними .NET SP).

System.Timer.Timer реализован с использованием System.Threading.Timer, поэтому это объясняет, почему вы видите такие же результаты. Я предполагаю, что таймер реализован с использованием какого-то алгоритма планирования и т. Д. (Это внутренний вызов, возможно, просмотр Rotor 2.0 может пролить свет на него).

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

В противном случае вы можете взглянуть на мультимедийные таймеры (PInvoke).

person liggett78    schedule 06.01.2009

Если вы используете winmm.dll, вы можете использовать больше процессорного времени, но лучше контролировать.

Вот ваш пример, измененный для использования таймеров winmm.dll

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

И вот результат, который я получаю:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

Надеюсь это поможет.

person Justin Tanner    schedule 01.04.2009

Эти классы не предназначены для использования в реальном времени и зависят от характера динамического планирования операционной системы, такой как Windows. Если вам нужно выполнение в реальном времени, вы, вероятно, захотите взглянуть на какое-то встроенное оборудование. Я не уверен на 100%, но я думаю, что .netcpu может быть версией в реальном времени меньшей среды выполнения .NET на кристалле.

http://www.arm.com/markets/emerging_applications/armpp/8070.html

Конечно - вам нужно оценить, насколько важна точность этих интервалов, чтобы прикрепленный к ним код будет выполняться в операционной системе, не работающей в реальном времени. Если конечно это чисто академический вопрос (в таком случае - да, интересно!: P).

person Mitch Denny    schedule 06.01.2009

Похоже, что ваши фактические частоты таймера составляют 63,9 Гц или целые числа, кратные ему.

Это будет означать разрешение таймера около 15 мсек (или целые числа, кратные этому значению, то есть 30 мсек, 45 мсек и т. Д.).

Ожидается, что таймеры, основанные на целых числах, кратных тику (в DOS, например, значение тика было 55 мсек / 18 Гц).

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

person ChrisW    schedule 06.01.2009

Windows (и, следовательно, .NET, работающая поверх нее) - это операционная система с приоритетной многозадачностью. Любой данный поток может быть остановлен в любое время другим потоком, и если вытесняющий поток не ведет себя должным образом, вы не получите управление обратно, когда захотите или нужно.

Вкратце, вот почему вы не можете гарантировать получение точного времени и почему Windows и .NET не подходят для определенных типов программного обеспечения. Если жизни в опасности, потому что вы не получаете контроль ТОЧНО, когда вы этого хотите, выберите другую платформу.

person Mike Hofer    schedule 06.01.2009
comment
Что ж, это почти правда, есть более точные таймеры. Это не значит, что точное время невозможно только потому, что вы используете Windows. - person Factor Mystic; 08.04.2011

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

http://www.pharlap.com/rtx.htm

person kirkus    schedule 06.01.2009

Вот хорошая реализация таймера с использованием мультимедийного таймера http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

person Laureano    schedule 12.02.2010

Таймер не работает по множеству причин. Пример с оборудованием, тот же поток занят другой обработкой и так далее ...

Если вам нужно более точное время, используйте класс Stopwatch в пространстве имен System.Diagnostics.

person robertpnl    schedule 06.01.2009
comment
Спасибо за ответ, robertpnl. К сожалению, секундомер предназначен только для измерения времени, а не для периодических событий. А в примере кода загрузка ЦП близка к нулю, так что это не может быть проблемой. - person Daniel Wolf; 06.01.2009

Отчасти проблема в том, что таймеры должны учитывать две задержки. Это может варьироваться в зависимости от того, как таймер реализован в ОС.

  1. Время, которое вы просите подождать
  2. Время между моментом №1 и переходом процесса в очередь планирования.

Таймер хорошо контролирует №1, но почти не контролирует №2. Он может сигнализировать ОС, что он хотел бы запустить его снова, но ОС может разбудить его, когда захочет.

person JaredPar    schedule 06.01.2009

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

person Jonathan Allen    schedule 01.04.2009
comment
Я не думаю, что это будет более точным ... Поток с циклом может быть остановлен в любое время ОС, чтобы запустить что-то еще. - person Nathan; 12.02.2010
comment
Я предполагаю, что вы можете установить приоритет в реальном времени, использовать параллельный сборщик мусора и сделать требование, чтобы эта программа была установлена ​​только на многоядерных машинах. Не идеально, но, вероятно, настолько близко, насколько вы можете получить, прежде чем переходить к ядру. - person Jonathan Allen; 14.02.2010
comment
Установка приоритета процесса на Realtime указывает ОС, что вы никогда не хотите, чтобы процесс передавал процессорное время другому процессу. Если ваша программа случайно войдет в бесконечный цикл, вы можете обнаружить, что даже операционная система заблокирована, и вам не останется ничего, кроме кнопки питания! По этой причине High обычно является лучшим выбором для приложений реального времени. НО. Есть много проблем и с High: если ваше приложение реального времени имеет пользовательский интерфейс, повышение приоритета процесса приводит к чрезмерному увеличению времени ЦП для обновлений экрана. Так что использование MMTimers для этого случая - САМОЕ ЛУЧШЕЕ. - person MajesticRa; 25.04.2011