C # Почему частоты таймера крайне отключены? - PullRequest
14 голосов
/ 06 января 2009

И 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.

Есть идеи?

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

Ответы [ 10 ]

23 голосов
/ 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

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

5 голосов
/ 06 января 2009

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

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

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

4 голосов
/ 06 января 2009

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

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

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

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

3 голосов
/ 06 января 2009

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

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

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

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

3 голосов
/ 06 января 2009

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

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

2 голосов
/ 12 февраля 2010

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

2 голосов
/ 06 января 2009

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

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

1 голос
/ 06 января 2009

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

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

0 голосов
/ 01 апреля 2009

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

0 голосов
/ 06 января 2009

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

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

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

...