# Timers
System.Threading.Timer — Simplest multithreaded timer. Contains two methods and one constructor.
Example: A timer calls the DataWrite method, which writes "multithread executed. " after five seconds have elapsed, and then every second after that until the user presses Enter:
Note : Will post a separate section for disposing multithreaded timers.
Change — This method can be called when you would like change the timer interval.
Timeout.Infinite — If you want to fire just once. Specify this in the last argument of the constructor.
System.Timers — Another timer class provided by .NET Framework. It wraps the System.Threading.Timer .
# Features:
- IComponent — Allowing it to be sited in the Visual Studio’s Designer’s component tray
- Interval property instead of a Change method
- Elapsed event instead of a callback delegate
- Enabled property to start and stop the timer ( default value = false )
- Start & Stop methods in case if you get confused by Enabled property (above point)
- AutoReset — for indicating a recurring event ( default value = true )
- SynchronizingObject property with Invoke and BeginInvoke methods for safely calling methods on WPF elements and Windows Forms controls
Example representing all the above features:
Multithreaded timers — use the thread pool to allow a few threads to serve many timers. It means that callback method or Elapsed event may trigger on a different thread each time it is called.
Elapsed — this event always fires on time—regardless of whether the previous Elapsed event finished executing. Because of this, callbacks or event handlers must be thread-safe. The accuracy of multithreaded timers depends on the OS, and is typically in the 10–20 ms.
interop — when ever you need greater accuracy use this and call the Windows multimedia timer. This has accuracy down to 1 ms and it is defined in winmm.dll .
timeBeginPeriod — Call this first to inform OS that you need high timing accuracy
timeSetEvent — call this after timeBeginPeriod to start a multimedia timer.
timeKillEvent — call this when you are done, this stops the timer
timeEndPeriod — Call this to inform the OS that you no longer need high timing accuracy.
You can find complete examples on the Internet that use the multimedia timer by searching for the keywords dllimport winmm.dll timesetevent .
# Creating an Instance of a Timer
Timers are used to perform tasks at specific intervals of time (Do X every Y seconds) Below is an example of creating a new instance of a Timer.
NOTE: This applies to Timers using WinForms. If using WPF, you may want to look into DispatcherTimer
# Assigning the "Tick" event handler to a Timer
All actions performed in a timer are handled in the "Tick" event.
Как создать таймер в c
Одним из важнейших классов, находящихся в пространстве имени System.Threading, является класс Timer . Данный класс позволяет запускать определенные действия по истечению некоторого периода времени.
Например, нам надо запускать какой-нибудь метод через каждые 2000 миллисекунд, то есть раз в две секунды:
Первым делом создается объект делегата TimerCallback, который в качестве параметра принимает метод. Причем данный метод должен в качестве параметра принимать объект типа object .
И затем создается таймер. Данная перегрузка конструктора таймера принимает четыре параметра:
объект делегата TimerCallback
объект, передаваемый в качестве параметра в метод Count
количество миллисекунд, через которое таймер будет запускаться. В данном случае таймер будет запускать немедленно после создания, так как в качестве значения используется 0
интервал между вызовами метода Count
И, таким образом, после запуска программы каждые две секунды будет срабатывать метод Count.
Если бы нам не надо было бы использовать параметр obj у метода Count, то при создании таймера мы могли бы указывать в качестве соответствующего параметра знаечние null : Timer timer = new Timer(tm, null, 0, 2000);
Программирование на C, C# и Java
Уроки программирования, алгоритмы, статьи, исходники, примеры программ и полезные советы
ОСТОРОЖНО МОШЕННИКИ! В последнее время в социальных сетях участились случаи предложения помощи в написании программ от лиц, прикрывающихся сайтом vscode.ru. Мы никогда не пишем первыми и не размещаем никакие материалы в посторонних группах ВК. Для связи с нами используйте исключительно эти контакты: vscoderu@yandex.ru, https://vk.com/vscode
Исходный код: таймер на C# в Windows Forms
Разбираем создание таймера на языке программирования C# в приложении Windows Forms. Полный исходный код с подробными комментариями можно будет скачать внизу страницы.
Для начала в Windows Forms создаём внешнюю оболочку программы. У нас она выглядит вот так:

Здесь у нас 8 Label’ов, 3 TextBox’a, 3 Buttom’a и сам Timer.
Примечание: при переносе элемента Timer в форму, на неё ничего не появляется. Лишь в нижней части окна программы под формой появляется значок
, не пугайтесь.
Щёлкнем на значок таймера
и в окне «Свойства» в группе «Поведение» устанавливаем значение параметра Interval равным 1000. Данный параметр определяет длину тика таймера в миллисекундах, указав 1000, мы сделали один тик равным одной секунде.
После оформления и настройки приступаем к коду. Вводим целочисленные переменные h — часы, m- минуты, s — секунды.
Затем дважды щёлкаем мышью на кнопке «Старт» и переходим на участок кода, отвечающий за клик на эту кнопку.
Таймер в .NET с интервалом 1 мс. Windows

Вы пишите код на платформе .NET под Windows и вам нужно выполнять некоторые действия каждую миллисекунду. Возможно ли это? Какие есть варианты и насколько они надёжны? Разберёмся, что можно использовать, и какие есть гарантии по точности срабатывания. Статья сконцентрирована на поиске такого решения, которое работало бы и под .NET Framework, и под .NET Core / .NET, и в разных версиях ОС, и являлось бы механизмом общего назначения (а не только для программ с GUI, например).
Для чего вообще может потребоваться таймер с малым периодом? Примером могут служить различные программные аудио- и видеоплееры. Классический подход при воспроизведении мультимедийных данных – раз в N единиц времени смотреть, что́ нужно подать на устройство вывода (видео-, звуковую карту и т.д.) в данный момент времени, и при необходимости отсылать новые данные (кадр, аудиобуфер) на это устройство. В таких случаях информация часто расположена достаточно плотно (особенно в случае с аудио), а временны́е отклонения в её воспроизведении хорошо заметны ушам, глазам и прочим человеческим органам. Поэтому N выбирается небольшим, измеряется в миллисекундах, и часто используется значение 1.
Я разрабатываю библиотеку для работы с MIDI – DryWetMIDI. Помимо взаимодействия с MIDI файлами, их трансформации и сопряжения с музыкальной теорией, библиотека предлагает API для работы с MIDI устройствами, а также средства для воспроизведения и записи MIDI данных. DryWetMIDI написана на C#, а мультимедийный API реализован для Windows и macOS. Вкратце воспроизведение в библиотеке работает так:
все MIDI-события снабжаются временем, когда они должны быть воспроизведены, время измеряется в миллисекундах и отсчитывается от начала всех данных (т.е. от 0);
указатель P устанавливается на первое событие;
запускается счётчик времени C ;
запускается таймер T с интервалом 1 мс;
при каждом срабатывании T : a) если время воспроизведения текущего события (на которое указывает P ) меньше или равно текущему времени, взятому из C , послать событие на устройство; если нет – ждать следующего тика таймера; b) сдвинуть P вперёд на одно событие и вернуться на a.
Итак, нам нужен таймер, срабатывающий с интервалом 1 мс. В этой статье мы посмотрим, что нам предлагает Windows для решения данной задачи, и как мы можем использовать это в .NET.
К слову, можно легко проверить, что привычные нам программные продукты для воспроизведения аудио и видео используют таймер с малым интервалом. В Windows есть встроенная утилита Powercfg, позволяющая получать данные по энергопотреблению, и в частности, какие программы запрашивают повышение разрешения (= понижение интервала) системного таймера.
Например, запустив Google Chrome и открыв любое видео в YouTube, выполните команду
powercfg /energy /output C:\report.html /duration 5
В корне диска C будет создан файл с отчётом report.html. В отчёте увидим такую запись:
Platform Timer Resolution:Outstanding Timer Request
A program or service has requested a timer resolution smaller than the platform maximum timer resolution.
Requested Period 10000
Requesting Process ID 2384
Requesting Process Path\Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
Браузер запросил новый период системного таймера 10000. Единицы этого значения – сотни наносекунд (как бы это ни было странно). Если перевести в миллисекунды, то получим как раз 1.
Или же при воспроизведении аудиофайла в Windows Media Player:
Platform Timer Resolution:Outstanding Timer Request
A program or service has requested a timer resolution smaller than the platform maximum timer resolution.
Requested Period 10000
Requesting Process ID 11876
Requesting Process Path\Device\HarddiskVolume3\Program Files (x86)\Windows Media Player\wmplayer.exe
Любопытно, что, например, VLC использует интервал 5 мс:
Platform Timer Resolution:Outstanding Timer Request
A program or service has requested a timer resolution smaller than the platform maximum timer resolution.
Requested Period 50000
Requesting Process ID 25280
Requesting Process Path\Device\HarddiskVolume3\Program Files\VideoLAN\VLC\vlc.exe
Есть подозрение (непроверенное), что частота таймера зависит от частоты кадров видео. А быть может, разработчики видеоплеера просто посчитали наглостью всегда запрашивать 1 мс. И, возможно, они правы.
Подготовка тестового кода
Создадим каркас наших тестов. Опишем интерфейс таймера:
Метод Start принимает первым параметром интервал таймера. Я решил проверить работу таймеров не только для интервала 1 мс, но также и для 10 и 100 мс. Вторым параметром будем передавать метод, который будет выполняться при срабатывании таймера.
Все наши проверки сделаем в одном классе:
в течение 3 минут складываем с него время при каждом срабатывании таймера в список;
собираем интервалы между собранными временами;
записываем полученные дельты в текстовый файл deltas_<interval>.txt.
Далее по этим наборам дельт строим графики, чтобы наглядно видеть, насколько точно срабатывает таймер. Содержимое каждого графика будет таким:

Типичный график интервалов между срабатываниями таймера
Справа сверху будет отображаться процент “хороших” результатов – дельт, попадающих в 10-процентную окрестность вокруг заданного интервала. Число 10 выбрано навскидку, но, как мы увидим, оно вполне помогает понять разницу между таймерами.
Если не сказано явно, запуск тестов производится на виртуальных машинах Azure Pipelines из пула Microsoft с операционной системой Microsoft Windows Server 2019 (10.0.17763). Иногда будем смотреть на моей локальной машине с ОС Windows 10 20H2 (сборка 19042.1348). Windows 11 под рукой нет, быть может, кому-то будет интересно проверить там.
Я решил сделать тест каждого варианта в виде отдельного консольного приложения. Все эти приложения собрал вместе в солюшне в виде проектов. Все ссылки на код, данные и графики будут приведены в конце статьи. А мы начинаем наше исследование.
EDIT ────────
В комментариях было высказано много интересных идей и предложений. Не все из них меняют итоги статьи, но для полноты картины упомянуть стоит. Кроме того, будут добавлены графики загрузки процессора. Такие вставки будут оформлены, как этот абзац.
Бесконечный цикл
Нельзя обойти стороной наивный подход – таймер на основе бесконечного цикла с подсчётом интервала:
Запустив тест с этим таймером
получим, разумеется, отличные результаты. Например, для 1 мс:

1 мс
И хоть результаты не могут не радовать, данный таймер, конечно же, не стоит использовать в реальных приложениях. Все мы знаем, что он будет попусту загружать CPU и расходовать батарею на портативных устройствах. Например, на моей машине с 4 ядрами получим такую загрузку процессора:

Загрузка процессора на бесконечном цикле
То есть примерно одно ядро. Графики для других интервалов приводить не буду, там аналогичные картины (кто хочет, может посмотреть по ссылке в конце статьи).
EDIT ────────
Как ни странно, именно бесконечный цикл вызвал в комментариях наибольшую активность. Было предложено несколько вариантов, рассмотрим каждый.
Во-первых, не один человек уверенно высказался о том, что вызов метода Thread.Yield должен снизить загрузку процессора. Что ж, напишем новый таймер:
Точность будет высокая (смотрел на своём локальном компьютере)

1 мс
но вот загрузка процессора не меняется

Загрузка CPU
Результаты аналогичные и для виртуалок Azure Pipelines и для второго компьютера. Т.е. совершенно точно нельзя назвать такой таймер хорошим.
Во-вторых, в комментариях были упомянуты NtSetTimerResolution / NtDelayExecution . Это недокументированные функции системной библиотеки ntdll.dll. Я модифицировал бесконечный цикл простейшим образом с использованием этих функций, сделав такой таймер:
Такая реализация обладает некоторыми недостатками (например тем, что срабатывания будут «плыть» в зависимости от времени выполнения callback ), но для демонстрации вполне годится. Запустив, получим такие результаты на локальной машине:
1 мс
10 мс
На виртуалках Azure Pipelines результаты тоже отличные, хотя среднее значение и не совпадает с запрошенным интервалом. Например, для интервала 1 мс:

1 мс
То бишь такой таймер действительно неплох.
Переходим к стандартным классам таймеров в .NET.
System.Timers.Timer
получим такие результаты:
1 мс
10 мс
100 мс
EDIT ────────
Загрузка CPU, разумеется околонулевая, ибо таймер работает на пуле потоков:

1 мс
Как видим, для малых интервалов 15.6 мс – наилучший средний показатель. Как известно, это стандартное разрешение системного таймера Windows, о чём можно подробно прочитать в документе от Microsoft под названием Timers, Timer Resolution, and Development of Efficient Code (кстати, очень интересный и полезный материал, рекомендую к прочтению):
The default system-wide timer resolution in Windows is 15.6 ms, which means that every 15.6 ms the operating system receives a clock interrupt from the system timer hardware.
А в документации по классу явно сказано:
The System.Timers.Timer class has the same resolution as the system clock. This means that the Elapsed event will fire at an interval defined by the resolution of the system clock if the Interval property is less than the resolution of the system clock.
Так что результаты не выглядят удивительными.
Документ выше датируется 16 июня 2010 года, однако не утерял своей актуальности. В нём также сказано:
The default timer resolution on Windows 7 is 15.6 milliseconds (ms). Some applications reduce this to 1 ms, which reduces the battery run time on mobile systems by as much as 25 percent.
Эта важная информация явно говорит о том, что понижение интервала системного таймера напрямую влияет на энергопотребление и время работы устройств от батареи. Возвращаясь к примеру с VLC из начала статьи, подход с 5 мс выглядит разумным.
Но всё же в некоторых ситуациях необходим период 1 мс, в частности для воспроизведения аудио- и MIDI-данных. В числе прочего в документе написано, как можно повысить разрешение системного таймера:
Applications can call timeBeginPeriod to increase the timer resolution. The maximum resolution of 1 ms is used to support graphical animations, audio playback, or video playback.
Т.е., согласно приведённому тексту, можно вызвать функцию timeBeginPeriod, запустить таймер с заданным интервалом, и даже стандартные таймеры должны срабатывать с этим интервалом. Что ж, проверим.
System.Timers.Timer + timeBeginPeriod
Код нового таймера:
Не буду здесь приводить код класса NativeTimeApi , кому интересно, посмотрит его в архиве с солюшном (ссылка в конце статьи). Запускаем тест:
1 мс
10 мс
100 мс
Увы, лучше не стало. Если немного погуглить, обнаружим, что мы не одиноки в своём горе:
Оказывается, начиная с версии Windows 10 2004 изменилось влияние функции timeBeginPeriod на стандартные таймеры. А именно, теперь она на них не влияет. По этой теме можно почитать интересную статью – Windows Timer Resolution: The Great Rule Change. К слову, выглядит, что проблема присутствует и на более ранних версиях Windows 10.
Кстати говоря, визуально результаты всё же стали немного другими. А именно, уменьшился разброс относительно среднего значения. Это может быть случайностью, а может быть и влиянием функции timeBeginPeriod .
EDIT ────────

1 мс
System.Threading.Timer
Для полноты картины нужно также посмотреть, а как обстоят дела с System.Threading.Timer. Код:
1 мс
10 мс
100 мс
EDIT ────────
Процессор нагружен аналогично предыдущему таймеру:

1 мс
Ожидаемо никаких отличий от System.Timers.Timer, так как в документации нам явно говорят об этом:
The Timer class has the same resolution as the system clock. This means that if the period is less than the resolution of the system clock, the TimerCallback delegate will execute at intervals defined by the resolution of the system clock…
System.Threading.Timer + timeBeginPeriod
Работа System.Threading.Timer с предварительным вызовом timeBeginPeriod (а вдруг с этим таймером сработает):
1 мс
10 мс
100 мс
EDIT ────────

1 мс
Multimedia timer
В Windows издревле существует API для создания мультимедийных таймеров. Использование их состоит в регистрации функции обратного вызова с помощью timeSetEvent и предварительном вызове timeBeginPeriod . Таким образом, опишем новый таймер:
Запустив тест, получим такие результаты:
1 мс
10 мс
100 мс
А вот это уже интересно. Проверим на локальной машине:
1 мс
10 мс
100 мс
Тут вообще красота. Таймер прекрасно держит заданный интервал, лишь изредка заметно отклоняясь от него (чего избежать невозможно на Windows, ибо эта ОС не является системой реального времени).
EDIT ────────
Дополним результаты графиками загрузки процессора. На локальной машине:

1 мс
На виртуалке Azure Pipelines:

1 мс
Нагрузка на процессор минимальная.
Итак, результаты радуют. Однако, в документации сказано, что функция timeSetEvent устаревшая:
This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.
Вместо неё предлагается использовать функцию CreateTimerQueueTimer. Что ж, мы, как законопослушные разработчики идём пробовать.
Timer-queue timer
Вместо мультимедийных таймеров Microsoft рекомендует использовать таймеры, созданные на специальных очередях. Можно использовать дефолтную очередь, а можно и создавать свои. Мы будем использовать дефолтную. Код нашего таймера:
Здесь в параметр Flags функции CreateTimerQueueTimer мы передаём WT_EXECUTEDEFAULT . Чуть позже посмотрим и на другой флаг. А пока запустим тест:
1 мс
10 мс
100 мс
Выглядит многообещающе. Проверим на локальной машине:
1 мс
10 мс
100 мс
Как ни странно, в разных версиях Windows таймер работает по-разному. На моей Windows 10 результаты не лучше стандартных .NET таймеров.
EDIT ────────
Любопытно, будет ли отличаться загрузка процессора в разных средах. На локальной машине:

1 мс
На виртуалке Azure Pipelines:

1 мс
Процессор нагружается одинаково при том, что точность разная. Не буду приводить графики загрузки CPU в дальнейших разделах, там всё то же самое.
Timer-queue timer + timeBeginPeriod
Интереса ради я проверил предыдущий таймер с предварительной установкой периода системного таймера на локальной машине:
1 мс
10 мс
100 мс
Внезапно на 10 мс неплохие результаты. Но для 1 мс всё так же плохо.
Timer-queue timer + WT_EXECUTEINTIMERTHREAD
В прошлый раз мы использовали опцию WT_EXECUTEDEFAULT при создании таймера. Попробуем установить другую – WT_EXECUTEINTIMERTHREAD . Результаты (по-прежнему используем локальную машину):
1 мс
10 мс
100 мс
И хотя ничего нового, любопытно, что у таймеров на очередях очень малый разброс значений. Практически все дельты попадают в чёткий диапазон.
Timer-queue timer + WT_EXECUTEINTIMERTHREAD + timeBeginPeriod
Без лишних слов:
1 мс
10 мс
100 мс
Глядя на графики, я всё-таки прихожу к выводу, что timeBeginPeriod как-то да влияет на таймеры. Коридор значений для интервала 1 мс явно становится уже.
Итоги
Буду честен, рассмотрены не все варианты. Вот тут в блоке Tip перечислены ещё такие:
Но и это ещё не всё. В .NET 6 появился PeriodicTimer. Зоопарк разных таймеров в .NET и Windows, конечно, весьма солидный.
Но все эти таймеры не подходят. Как я писал до ката: статья сконцентрирована на поиске такого решения, которое работало бы и под .NET Framework, и под .NET Core / .NET, и в разных версиях ОС, и являлось бы механизмом общего назначения. А потому вот причины отказа от упомянутых классов (по крайней мере для нужд мультимедиа):
System.Windows.Forms.Timer – выполняется на UI-потоке, привязка к приложениям с GUI;
System.Web.UI.Timer – только .NET Framework, да и Web.UI в имени пространства имён не вяжется с решением общего назначения;
А что же можно сказать о тех таймерах, что были проверены в статье? Нет смысла писать много букв, единственный надёжный вариант – мультимедийные таймеры. И хотя они давно объявлены устаревшими, только они соответствуют критериям, указанным до ката.
EDIT ────────
Видя результаты работы таймера, основанного на функциях NtSetTimerResolution / NtDelayExecution должен признать, что это также отличный вариант достичь точности в 1 мс. Более того, таким образом можно достичь и большей точности, чего невозможно сделать с мультимедийными таймерами. Большое спасибо @Alexx999 и всем неравнодушным к теме!