Вопрос

Оба 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 использует пул потоков, поэтому каждое событие запускается в свободном потоке.

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

Решение

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

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

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

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

Другие советы

Если вы используете 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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