С# Почему частоты таймера отключены?
Оба 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
, это меня озадачивает.
Btw, это не случайные отклонения. Они одинаковые значения каждый раз.
И аналогичная тестовая программа для другого класса таймера System.Threading.Timer
показывает те же самые результаты.
В моей реальной программе мне нужно собирать измерения ровно 50 выборок в секунду. Это еще не требует системы реального времени. И очень сложно получить 32 выборки в секунду вместо 50.
Любые идеи?
: @Крис
Вы правы, все интервалы кажутся целыми кратными примерно 1/64-й секунде. Btw, добавив Thread.Sleep(...) в обработчик события, не имеет никакого значения. Это имеет смысл, учитывая, что System.Threading.Timer
использует пул потоков, поэтому каждое событие запускается в свободном потоке.
Ответы
Ответ 1
Ну, я получаю различное количество до 100 Гц на самом деле с некоторыми большими отклонениями, но в большинстве случаев ближе к запрашиваемому номеру (работает XP SP3 с последними .NET SP).
System.Timer.Timer реализуется с использованием System.Threading.Timer, поэтому это объясняет, почему вы видите одинаковые результаты. Я полагаю, что таймер реализован с использованием какого-то алгоритма планирования и т.д. (Внутренний вызов, возможно, просмотр Rotor 2.0 может пролить свет на него).
Я предлагаю реализовать какой-то таймер, используя другой поток (или их комбинацию), вызывающий "Сон" и обратный вызов. Однако не уверен в результатах.
В противном случае вы можете взглянуть на мультимедийные таймеры (PInvoke).
Ответ 2
Если вы используете 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
Надеюсь, что это поможет.
Ответ 3
Эти классы не предназначены для использования в режиме реального времени и зависят от динамического планирования операционной системы, такой как Windows. Если вам нужно выполнение в реальном времени, вы, вероятно, захотите взглянуть на некоторые встроенные аппаратные средства. Я не уверен на 100%, но я думаю, что .netcpu может быть в режиме реального времени меньшей средой .NET на чипе.
http://www.arm.com/markets/emerging_applications/armpp/8070.html
Конечно, вам нужно оценить, насколько важна точность этих интервалов, что код, прикрепленный к ним, будет выполняться в операционной системе, отличной от реального времени. Если, конечно, это не чисто академический вопрос (в этом случае - да, это интересно!: P).
Ответ 4
Похоже, ваши фактические частоты таймера составляют 63,9 Гц или их целые числа.
Это означало бы разрешение по таймеру около 15 мс (или его целые числа, т.е. 30 мс, 45 мс и т.д.).
Это означает, что следует ожидать таймеры на основе целочисленных кратных значения "тика" (в DOS, например, значение "tick" составляло 55 мс /18 Гц).
Я не знаю, почему ваш тик-счет составляет 15.65 мсек вместо ровного 15 мс. В качестве эксперимента, что, если вы спите в течение нескольких мсек в обработчике таймера: мы можем видеть 15 мс между тиками и 0,65 мс в вашем обработчике таймера при каждом тике?
Ответ 5
Windows (и, следовательно,.NET, работающий поверх нее) - это операционная система с многозадачностью с упреждающим многозадачностью. Любой данный поток может быть остановлен в любой момент другим потоком, и если поток вытеснения не ведет себя правильно, вы не получите контроль обратно, когда захотите, или вам понадобится.
Вкратце, почему вы не можете гарантировать точное время и почему Windows и .NET являются неподходящими платформами для определенных типов программного обеспечения. Если жизнь в опасности, потому что вы не получите контроль ТОЧНО, когда захотите, выберите другую платформу.
Ответ 6
Если вам нужно сделать переход в среду реального времени, я использовал RTX в прошлом, когда требовалась детерминированная выборка (с помощью пользовательского последовательного устройства), и ей очень повезло.
http://www.pharlap.com/rtx.htm
Ответ 7
Вот хорошая реализация таймера с использованием таймера мультимедиа
http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html
Ответ 8
Есть много причин, по которым частоты таймера отключены. Пример с оборудованием, тот же поток занят другой обработкой и так далее...
Если вы хотите более точное время, используйте класс Stopwatch в пространстве имен System.Diagnostics.
Ответ 9
Отчасти проблема заключается в том, что таймеры имеют две задержки для учетной записи. Это может варьироваться в зависимости от того, как таймер реализован в ОС.
- Время ожидания запроса
- Время между тем, когда встречается # 1, и процесс переходит в очередь планирования.
Таймер имеет хороший контроль над # 1, но почти не контролирует # 2. Он может сигнализировать ОС, что он хотел бы запустить снова, но ОС может разбудить его, когда ему это нравится.
Ответ 10
Исходя из ваших комментариев, вы не должны использовать таймер вообще. Вы должны использовать цикл с секундомером, чтобы проверить интервал и спин-блокировку, чтобы вы не потеряли квант.