Представить себе задачу, для которой нужен таймер несложно, но вот зачем нужны разные их виды? Ведь на первый взгляд таймер настолько очевидный и простой концепт, что трудно представить возможные и при этом существенные различия между их реализациями. Однако, в C# на данный момент предоставляет аж 6 классов реализующих таймер. Давайте же рассмотрим их подробней и поймём в чём разница между ними.
Если кратко — разные таймеры используются в разных местах и из-за этого могут потребоваться различные методы обращения к ним. Ниже приведён список таймеров и те места, где следует их использовать.
System.Windows.Threading.DispatcherTimer | Для интерфейса в WPF |
System.UI.XAML.DispatcherTimer | Для UWP приложений |
System.Web.UI.Timer | Для Web |
System.Windows.Form.Timer | Для WinForms |
System.Timers.Timer | Для серверного приложения (и унаследован от компонента) |
System.Threading.Timer | Базовый таймер |
Таб.1 Список таймеров C#
Рассмотрим каждый из них поподробней и начнём с базового таймера.
При использовании System.Threading.Timer в первом параметре конструктора передаётся метод-слушатель, который будет вызван при срабатывании таймер. На этот метод накладываются определённые требования делегата TimerCallback, согласно которым возвращаемым типом должен быть void, а типом параметра — object.
static void CallBackMethod(object state)
Также конструктор может содержать другие параметры:
- втором параметре может передаваться любой объект, который затем будет получен с аргументом объекта в методе обратного вызова
- третий параметр определяет промежуток времени через которые callback должен быть вызван повторно
- в четвертом параметре должно быть указано значение -1.
Для изменения промежутка времени после создания объекта Timer служит метод Change(). В целом этого достаточно, для того, чтобы начать использовать данный таймер.
Данный таймер чаще всего встречается в консольных приложениях и, пожалуй, является самым одним из первых таймеров с которым знакомятся разработчики. При этом, что иронично, System.Threading.Timer не является потокобезопасным, в отличии от следующего.
System.Timers.Timer по сути является обёрткой над System.Threading.Timer, которая предоставляет некоторые дополнительные функции, используемые для диспетчеризации в определенном потоке, в частности, добавление/удаление слушателей после инициализации. Другим же важным отличием является то, что данный таймер также наследуется от класса Component. Это позволяет использовать его в UI, однако для этого лучше подходят другие таймеры.
System.Windows.Form.Timer, System.Web.UI.Timer, System.UI.XAML.DispatcherTimer и System.Windows.Threading.DispatcherTimer созданы для использования в настольных графических и веб приложениях. В целом же их можно рассматривать как один таймер, поскольку они крайне похожи в применении и разные классы нужны для того, чтобы обеспечить реализацию одних и тех же свойств и методов в различных видах приложений. Общего же у них — поддержка размещения IComponent в IContainer,
В общем виде и для большей наглядности можно свести основные отличия между таймерами в следующую таблицу:
Описание возможностей | System. Timers | System. Threading | System. Windows. Forms | System. Web.UI | System. Windows. Threading | System. UI.XAML |
Поддерживает добавление/удаление слушателя после создания экземпляра таймера | Да | Нет | Да | Да | Да | Да |
Поддерживает обратный вызов из UI-потока | Да | Нет | Да | Да | Да | Да |
Обратный вызов потоков вызванных из пула потоков | Да | Да | Нет | Нет | Нет | Нет |
Поддержка drag-and-drop в UI редакторе | Да | Нет | Да | Нет | Да | Да |
Подходит для работы в многопоточной среде сервера | Да | Да | Нет | Нет | Нет | Нет |
Включает поддержку передачи произвольного состояния от инициализации таймера к обратному вызову | Нет | Да | Нет | Нет | Нет | Нет |
Поддержка размещения IComponent в IContainer | Да | Нет | Да | Да | Да | Да |
Таб. 2 Отличия таймеров
При этом, несмотря на все отличия у всех таймеров есть и общие места, такие как реализация IDisposable, поддержка разовых и периодически повторяющиеся обратных вызовов, возможность доступа через границы домена приложения. Таким образом, можно сказать, что взаимодействие с различными таймерам весьма похоже и неопытный разработчик может даже не заметить отличий в некоторых ситуациях и использовать не совсем подходящие таймеры.
При этом, также стоит отметить, что разные типы таймеров по разному работают с потоками. Так например при использовании System.Timers.Timer работа с ним всегда выносится в отдельный поток, общий для всех таймеров данного типа (т.е. при использовании нескольких таймеров будет создан лишь один поток). Это необходимо для того, чтобы с одной стороны блокировка исходного потока (например, трудоёмкими вычислениями или запросами) не влияла на работу таймеров, а с другой для экономии ресурсов на создании и поддержании работы потоков. А вот остальные таймеры «живут» не в отдельном потоке, а UI-потоке, что позволяет им иметь доступ к элементам интерфейса.
Таким образом можно сформулировать следующие рекомендации по применению таймеров:
- Для приложений без UI по умолчанию лучше использовать более легковесный System.Threading.Timer, но если требуется потокобезопасность System.Timers.Timer будет лучшим выбором.
- Если нужно использование в UI, то следует применять соответствующий таймер, что позволит взаимодействовать с элементами UI напрямую.
- System.Timers.Timer может использоваться в UI, однако так как он является асинхронным, то выполняется в отдельном потоке и это вызывает ряд сложностей при работе с компонентами UI и переменными.
В завершении также скажу пару слов о .Net Core. Поскольку на данный момент .Net Core не поддерживает создание десктоп-приложений с графическим интерфейсом из коробки и без применения, то и соответствующих таймеров там нет (собственно получается, что и не надо). Однако, в .NET Core 3.0 обещают поддержку Windows Forms и WPF в компоненте Windows Desktop и вот там скорее всего они будут. При этом важно понимать, что этот компонент будет доступен только для ОС Windows.