Таймеры часто играют важную роль как в клиентских приложениях, так и в компонентах программ, основанных на серверах (включая службы Windows). Написание эффективного, управляемого кода с использованием измерения реального времени требует ясного представления процесса выполнения программы и глубокого знания тонкостей многопоточной модели .NET-библиотеки. Библиотека классов .NET (.NET Framework Class Library, или .NET FCL) предоставляет 3 различные класса таймеров: System.Windows.Forms.Timer, System.Timers.Timer и System.Threading.Timer. Каждый из этих классов разработан и оптимизирован для использования в разных ситуациях. Здесь рассмотрены эти 3 класса таймеров (перевод статьи [1], автор Alex Calvo, 2004 год), что поможет Вам понять, как и когда использовать соответствующие классы таймеров.
Объекты Timer в Microsoft Windows® позволяют Вам управлять, когда произойдет какое-либо действие в программе. Некоторые наиболее часто варианты применения таймеров: запустить процесс в запланированное время, установить интервалы между событиями, добиться нужной скорости анимации графики в интерфейсе пользователя (независимо от скорости работы процессора). В прошлом разработчики, работающие в Visual Basic®, даже использовали таймеры для симуляции многозадачности.
Вы должны были бы ожидать, что Microsoft .NET снабдит Вас нужными инструментами для реализации различных сценариев, связанных с отслеживанием реального времени. Как уже упоминалось, в .NET Framework Class Library для этого имеется 3 разных класса таймеров: System.Windows.Forms.Timer, System.Timers.Timer и System.Threading.Timer. Первые 2 класса доступны в окне тулбокса Visual Studio® .NET, что позволяет Вам бросить эти классы на разрабатываемую форму приложения и настроить их параметры - точно так же, как делается с любым визуальным компонентом GUI интерфейса. Если будете неосторожны, то уже в этом месте могут начаться проблемы.
Тулбокс Visual Studio .NET имеет компонент управления таймером и на закладке Windows Forms, и на закладке Components (см. рис. 1). Очень просто перепутать и использовать не тот компонент, что нужно, и еще хуже - не понять при этом, чем же отличаются разные компоненты. Использовать элемент управления timer control, который находится на закладке Windows Forms, следует только если он предназначен для редактора Windows Forms. Этот элемент управления соответствует созданию экземпляра класса System.Windows.Forms.Timer для Вашей формы (окно программы). Как и все другие элементы управления в тулбоксе, Вы можете либо позволить среде Visual Studio .NET обработать инициализацию класса, либо можете инициализировать экземпляр класса таймера вручную, в Вашем коде.
Рис. 1. Различные виды элементов управления таймеров (Timer Control).
Timer control, который находится на закладке Components, можно безопасно использовать в любом классе. Этот control создает экземпляр класса System.Timers.Timer. Если Вы используете тулбокс Visual Studio .NET, то можете безопасно использовать этот таймер либо с редактором форм для окон (Windows Forms designer), либо с редактором компонента класса (component class designer). Редактор компонента класса используется в Visual Studio .NET, когда Вы работаете с классом, который является производным классом от класса System.ComponentModel.Component (как в случае, когда Вы работаете со службами Windows). Класс System.Threading.Timer не виден в окне тулбокса Visual Studio .NET. Использование этого таймера несколько более сложное, однако предоставляет большую свободу, что Вы увидите дальше в этой статье.
Рис. 2. Пример приложения, использующего разные классы таймеров.
Давайте сначала рассмотрим классы System.Windows.Forms.Timer и System.Timers.Timer. У этих двух классов очень похожая объектная модель. Далее мы рассмотрим более продвинутый класс System.Threading.Timer. На рис. 2 показан скриншот демонстрационного приложения, на которое будут ссылки в этой статье. Приложение поможет Вам получить ясное представление о каждом из классов таймеров. Вы можете загрузить исходный код приложения по ссылке [2], и поэкспериментировать с ним.
[System.Windows.Forms.Timer]
Если Вы хотели бы получить метроном, то это не то, что нужно. События таймера, генерируемые этим классом, синхронны с остальным кодом Вашего приложения Windows Forms. Это означает, что код приложения, который выполняется, никогда не будет вытесняться экземпляром этого класса таймера (если при этом предположить, что Вы не вызывали в коде приложения Application.DoEvents). Точно так же, как и остальная часть кода обычного приложения Windows Forms, любой код, который находится в обработчике событий таймера (для этого типа класса таймера) выполняется с использованием потока интерфейса пользователя приложения (UI thread). Во время ожидания UI thread также отвечает за обработку всех сообщений в очереди сообщений Windows (Windows message queue). Это включает как сообщения Windows API, так и события тиков (Tick events), генерируемые этим классом таймера. Поток UI обработает эти сообщения каждый раз, когда программа приложения не занята чем-то еще (прим. переводчика: кроме того, на обработку событий таймера также может влиять поведение и других программ, особенно если они выполняются с более высоким приоритетом).
Если Вы писали раньше программы Visual Basic до появления Visual Studio .NET, то возможно знаете, что есть только один способ своевременно позволить потоку UI отвечать на события Windows, когда выполняется какой-либо код: вызывать в этом коде метод Application.DoEvents. Подобно Visual Basic, вызов Application.DoEvents из .NET Framework может привести к многим проблемам. Application.DoEvents уступает управление обработчику сообщений UI message pump, что позволяет обработать очередь ожидающих сообщений. Это может поменять ожидаемую последовательность выполнения кода. Если Application.DoEvents вызывается из Вашего кода, то поток выполнения программы может быть прерван, чтобы обработать события таймера, генерируемые экземпляром этого класса. Это может привести к неожиданному поведению программы, трудно поддающемуся отладке.
То, как этот класс таймера ведет себя, станет очевидно при запуске демонстрационного приложения [2]. Кликните на кнопку Start, затем на кнопку Sleep, и затем на кнопку Stop, и получите следующий вывод сообщений:
//Листинг 1, вывод сообщений при использовании System.Windows.Forms.Timer:
System.Windows.Forms.Timer Started @ 4:09:28 PM
--> Timer Event 1 @ 4:09:29 PM on Thread: UIThread
--> Timer Event 2 @ 4:09:30 PM on Thread: UIThread
--> Timer Event 3 @ 4:09:31 PM on Thread: UIThread
Sleeping for 5000 ms...
--> Timer Event 4 @ 4:09:36 PM on Thread: UIThread
System.Windows.Forms.Timer Stopped @ 4:09:37 PM
В этом демонстрационном приложении свойству Interval класса таймера System.Windows.Forms.Timer присвоено значение 1000 миллисекунд. Как Вы можете увидеть, если бы обработчик события таймера продолжал получать события таймера, в то время как главный поток UI спал (время сна установлено на 5 секунд), то мы увидели бы 5 событий таймера, выведенных в окно сообщения, по одному на каждую секунду. Но так не произошло - вместо этого таймер оставался в приостановленном состоянии, когда поток UI спал.
Применение класса System.Windows.Forms.Timer не могло бы быть проще - у него очень простой и интуитивно понятный интерфейс программирования. Методы Start и Stop в действительности предоставляют альтернативный способ установки свойства Enabled (которое само по себе является тонкой оберткой вокруг функций SetTimer / KillTimer интерфейса программирования Win32®). Свойство Interval, которое упоминалось ранее, своим именем говорит само за себя - оно устанавливает интервал срабатывания таймера в миллисекундах. Следует помнить, что даже если Вы можете установить свойство Interval в 1 миллисекунду, в соответствии с документацией на .NET Framework точность таймера не будет выше 55 миллисекунд (это время предоставлено потоку UI для обработки).
Захват событий, генерируемых экземпляром класса System.Windows.Forms.Timer, обрабатывается направлением события Tick на стандартный делегат EventHandler, как показано в следующем примере кода:
//Листинг 2, пример использования System.Windows.Forms.Timer:
System.Windows.Forms.Timer tmrWindowsFormsTimer = new System.Windows.Forms.Timer();
tmrWindowsFormsTimer.Interval = 1000;
tmrWindowsFormsTimer.Tick += new EventHandler(tmrWindowsFormsTimer_Tick);
tmrWindowsFormsTimer.Start();
...
private void tmrWindowsFormsTimer_Tick(object sender, System.EventArgs e)
{
// Выполнение каких-либо действий в контексте потока UI:
...
}
[System.Timers.Timer]
Документация .NET Framework описывает класс System.Timers.Timer как класс, разработанный и оптимизированный для использования в многопоточных рабочих окружениях (для применений в программах служб и серверов). Экземпляры этого класса таймера можно безопасно использовать из нескольких потоков. В отличие от System.Windows.Forms.Timer, класс System.Timers.Timer по умолчанию будет вызывать событие Вашего таймера на рабочем потоке (worker thread), полученном из пула потоков общеязыковой среды выполнения (common language runtime thread pool, пул потоков CLR). Это означает, что код внутри обработчика события Elapsed должен удовлетворять золотому правилу программирования Win32 (это правило часто доставляет головную боль неопытным разработчикам): к экземпляру любого элемента управления может получить только тот поток, который создал этот экземпляр элемента управления.
Класс System.Timers.Timer предоставляет простой путь решения этой дилеммы - он публикует свойство SynchronizingObject. Установка этого свойства в экземпляр Windows Form (или элемента управления Windows Form) гарантирует, что код в обработчике события Elapsed запустится в том же потоке, в котором был инициирован SynchronizingObject.
Если Вы используете тулбокс Visual Studio .NET, то среда Visual Studio .NET автоматически установит свойство SynchronizingObject в значение текущего экземпляра формы. Сначала может показаться, что использование этого класса таймера со свойством SynchronizingObject делает его функционально эквивалентным использованию класс System.Windows.Forms.Timer. Чаще всего так и есть. Когда операционная система оповещает класс System.Timers.Timer, что разрешенный таймер истек, таймер использует метод SynchronizingObject.Begin.Invoke для выполнения делегата события Elapsed на потоке, в котором создавался нижележащий дескриптор (handle) объекта SynchronizingObject. Этот обработчик события будет блокирован, пока поток UI не будет в состоянии обработать его. Однако, в отличие от System.Windows.Forms.Timer, событие все равно будет сгенерировано. Как Вы могли бы увидеть в листинге 1 ранее, System.Windows.Forms.Timer не может генерировать события, когда UI не может обработать их, в то время как System.Timers.Timer поставит события в очередь обработки, когда поток UI будет доступен.
//Листинг 3, использование свойства SynchronizingObject:
System.Timers.Timer tmrTimersTimer = new System.Timers.Timer();
tmrTimersTimer.Interval = 1000;
tmrTimersTimer.Elapsed += new ElapsedEventHandler(tmrTimersTimer_Elapsed);
tmrTimersTimer.SynchronizingObject = this;
//Синхронизация с текущей формой...
tmrTimersTimer.Start();
...
private void tmrTimersTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Выполнение каких-то действий в контексте потока UI (тот же поток,
// в котором была создана форма):
...
// Если мы не установили свойство SynchronizingObject, то код выполняется
// в контексте текущего потока (worker thread).
...
}
В листинге 3 показан пример, как использовать свойство SynchronizingObject. Вы можете использовать демонстрационное приложение, чтобы проанализировать поведение класса System.Timers.Timer. Для этого переведите радиокнопку в во вторую позицию, и выполните у же последовательность действий, которая была проделана ранее в обсуждении тестирования класса System.Windows.Forms.Timer. Вы увидите приблизительно такие сообщения:
//Листинг 4, тестирование класса System.Timers.Timer:
System.Timers.Timer Started @ 5:15:01 PM
--> Timer Event 1 @ 5:15:02 PM on Thread: WorkerThread
--> Timer Event 2 @ 5:15:03 PM on Thread: WorkerThread
--> Timer Event 3 @ 5:15:04 PM on Thread: WorkerThread
Sleeping for 5000 ms...
--> Timer Event 4 @ 5:15:05 PM on Thread: WorkerThread
--> Timer Event 5 @ 5:15:06 PM on Thread: WorkerThread
--> Timer Event 6 @ 5:15:07 PM on Thread: WorkerThread
--> Timer Event 7 @ 5:15:08 PM on Thread: WorkerThread
--> Timer Event 8 @ 5:15:09 PM on Thread: WorkerThread
System.Timers.Timer Stopped @ 5:15:10 PM
Как Вы можете видеть, здесь не были пропущены события таймера даже тогда, когда поток UI был в состоянии сна. Обработчик события Elapsed вызывался на каждом интервале. Несмотря на то, что поток UI спал, демонстрационное приложение вывело информацию о 5 произошедших событиях таймера (4 .. 8), во всех этих случаях поток UI просыпается, и снова может обработать очередь сообщений.
Как упоминалось ранее, члены класса System.Timers.Timer очень похожи на члены класса System.Windows.Forms.Timer. Самое большое отличие в том, что System.Timers.Timer это обертка над объектом ожидания таймера Win32, который генерирует событие Elapsed в контексте worker thread вместо генерации события Tick в контексте UI thread. Событие Elapsed должно быть соединено с обработчиком события, который соответствует делегату ElapsedEventHandler. Этот обработчик события принимает аргумент типа ElapsedEventArgs.
Кроме полей стандартного EventArgs, класс аргументов ElapsedEventArgs предоставляет public-свойство SignalTime, которое содержит точное истекшее время таймера. Поскольку этот класс поддерживает доступ из различных потоков, то можно вызвать метод Stop из другого потока, отличающегося от потока, который применяется для обработки события Elapsed. Потенциально это может привести к тому, что срабатывание события Elapsed произойдет даже после того, как был вызван метод Stop. Вы можете обработать эту ситуацию путем сравнения свойства SignalTime с временем, когда был вызван метод Stop.
Класс System.Timers.Timer также предоставляет свойство AutoReset, которое определяет должно ли событие Elapsed срабатывать с повторениями, или только 1 раз. Имейте в виду, что сброс свойства Interval после того, как таймер был запущен, приведет к тому, что текущий счетчик времени вернется обратно к нулевому значению. Например, если Вы установили интервал на 5, после чего прошло 3 секунды, и затем интервал был изменен на 10 секунд, то следующее событие таймера произойдет через 13 секунд, считая от последнего события таймера.
[System.Threading.Timer]
Третий класс таймера происходит из пространства имен System.Threading. Хотелось бы сказать: System.Threading.Timer самый лучший из всех классов таймеров, но это может ввести в заблуждение. С одной стороны, я был удивлен, что экземпляры этого класса по сути не ориентированы безопасное использование в многопоточном окружении, если учесть, что класс System.Threading.Timer находится в System.Threading namespace (очевидно, что это не означает, что класс System.Threading.Timer не может безопасно использоваться в многопоточном окружении). Интерфейс программирования этого класса не такой непротиворечивый, как у двух предыдущих рассмотренных классов таймеров, и несколько более громоздкий.
В отличие от двух предыдущих классов, класс System.Threading.Timer имеет 4 перегружаемых конструктора. Ниже показано, что это значит:
public Timer (TimerCallback callback, object state, long dueTime, long period);
public Timer (TimerCallback callback, object state, UInt32 dueTime, UInt32 period);
public Timer (TimerCallback callback, object state, int dueTime, int period);
public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);
Первый параметр (callback, функция обратного вызова) требует делегата TimerCallback, который указывает на метод со следующей сигнатурой:
public void TimerCallback (object state);
Второй параметр (state) может быть либо null, либо объектом, который содержит информацию, зависящую от приложения. Этот объект state передается в callback-функцию Вашего таймера всякий раз, когда возникает событие таймера. Имейте в виду, что callback-функция запускается в контексте рабочего потока (worker thread), так что Вы можете гарантировать, что имеется потокобезопасный способ доступа к объекту state.
Третий параметр (dueTime) позволяет указать, когда должно быть запущено начальное событие таймера. Вы можете указать 0, чтобы запустить таймер немедленно, или предотвратить таймер от автоматического запуска, если укажете здесь значение константы System.Threading.Timeout.Infinite.
Четвертый параметр (period) задает интервал (в миллисекундах), через который должна быть вызвана callback-функция. Если указать 0 или Timeout.Infinite, то это запретит последующие запуски событий таймера.
Как только конструктор был вызван, Вы все еще можете изменить настройки dueTime и period использованием метода Change. У этого метода также имеется четыре перезагрузки:
public bool Change (int dueTime, int period);
public bool Change (uint dueTime, uint period);
public bool Change (long dueTime, long period);
public bool Change (TimeSpan dueTime, TimeSpan period);
Ниже показан пример кода для запуска и остановки этого таймера (подобный код используется в демонстрационном приложении):
//Таймер инициируется так, что он не запустится автоматически:
System.Threading.Timer tmrThreadingTimer =
new System.Threading.Timer(new TimerCallback(tmrThreadingTimer_TimerCallback),
null,
System.Threading.Timeout.Infinite,
1000);
...
//Ручной запуск таймера:
tmrThreadingTimer.Change(0, 1000);
//Ручная остановка таймера:
tmrThreadingTimer.Change(Timeout.Infinite, Timeout.Infinite);
Как Вы могли бы ожидать, запуск демонстрационного приложения для класса System.Threading.Timer даст тот же вывод, который мы видели с классом System.Timers.Timer. Из-за того, что функция TimerCallback вызывается в контексте worker thread, нет пропущенных срабатываний таймера (подразумевается, что рабочие потоки могут запуститься). Листинг 5 показывает вывод приложения при тестировании System.Threading.Timer:
//Листинг 5, тестирование класса System.Threading.Timer:
System.Threading.Timer Started @ 7:17:11 AM
--> Timer Event 1 @ 7:17:12 AM on Thread: WorkerThread
--> Timer Event 2 @ 7:17:13 AM on Thread: WorkerThread
--> Timer Event 3 @ 7:17:14 AM on Thread: WorkerThread
Sleeping for 5000 ms...
--> Timer Event 4 @ 7:17:15 AM on Thread: WorkerThread
--> Timer Event 5 @ 7:17:16 AM on Thread: WorkerThread
--> Timer Event 6 @ 7:17:17 AM on Thread: WorkerThread
--> Timer Event 7 @ 7:17:18 AM on Thread: WorkerThread
--> Timer Event 8 @ 7:17:19 AM on Thread: WorkerThread
System.Threading.Timer Stopped @ 7:17:20 AM
В отличие от класса System.Timers.Timer, здесь нет аналога свойства SynchronizingObject, который был предоставлен классом System.Timers.Timer. Любые операции, которые потребуют доступа к элементам управления пользовательским интерфейсом (UI controls), должны быть корректно маршалированы с использованием методов Invoke или BeginInvoke элементов управления.
[Потокобезопасное программирование с использованием таймеров]
Для максимального повторного использования кода демонстрационное приложение вызывает один и тот же метод ShowTimerEventFired из всех трех разных типов событий таймера. Вот эти 3 обработчика события:
private void tmrWindowsFormsTimer_Tick (object sender,
System.EventArgs e)
{
ShowTimerEventFired (DateTime.Now, GetThreadName());
}
private void tmrTimersTimer_Elapsed (object sender,
System.Timers.ElapsedEventArgs e)
{
ShowTimerEventFired (DateTime.Now, GetThreadName());
}
private void tmrThreadingTimer_TimerCallback (object state)
{
ShowTimerEventFired (DateTime.Now, GetThreadName());
}
Как Вы можете видеть, метод ShowTimerEventFired берет текущее время и имя текущего потока в качестве своих аргументов. Чтобы отличить рабочие потоки (worker threads) от потока UI, главная точка входа приложения устанавливает свойство Name объекта CurrentThread в "UIThread". Метод-помощник GetThreadName возвратит либо значение Thread.CurrentThread.Name, либо "WorkerThread", если свойство Thread.CurrentThread.IsThreadPoolThread равно true.
Из-за того, что события таймера для System.Timers.Timer и System.Threading.Timer выполняются в контексте рабочих потоков (worker threads), при этом обязательно, чтобы любой код интерфейса пользователя в этих обработчиках маршалировался обратно в поток UI для обработки. Чтобы сделать это, автор создал делегата, которого назвал ShowTimerEventFiredDelegate:
private delegate void ShowTimerEventFiredDelegate (DateTime eventTime,
string threadName);
ShowTimerEventFiredDelegate позволяет методу ShowTimerEventFired вызвать самого себя обратно в поток UI. Листинг 6 показывает код, который все это делает.
//Листинг 6, метод ShowTimerEventFired:
private void ShowTimerEventFired (DateTime eventTime,
string threadName)
{
//InvokeRequired будет true, когда используется
// System.Threading.Timer или System.Timers.Timer
// (без SynchronizationObject)...
if (lstTimerEvents.InvokeRequired)
{
// Маршалинг этого callback к потоку UI (через
// экземпляр формы)...
BeginInvoke (new ShowTimerEventFiredDelegate(ShowTimerEventFired),
new object[] {eventTime, threadName});
}
else
lstTimerEvents.TopIndex = lstTimerEvents.Items.Add(
String.Format("—> Timer Event {0} @ {1} on Thread:
{2}",
++_tickEventCounter, eventTime.ToLongTimeString(),
threadName));
}
Очень просто определить, можете ли Вы безопасно получить доступ к элементу управления Windows Forms из текущего потока, путем опроса его свойства InvokeRequired. В этом примере если у ListBox-а свойство InvokeRequired==true, то можно использовать метод BeginInvoke формы для вызова метода ShowTimerEventFired снова через делегата ShowTimerEventFiredDelegate. Это гарантирует, что метод Add элемента управления ListBox выполнится в потоке UI.
Как можете видеть, здесь есть много проблем, которых Вам нужно избегать, когда программируете асинхронные события таймера. Автор рекомендует (перед использованием либо System.Timers.Timer, либо System.Threading.Timer) к прочтению статью Ian Griffith "Windows Forms: Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads" [3].
[Обработка реентрантности события таймера]
Здесь имеется другая тонкая проблема, которую Вам следует иметь в виду, когда работаете с асинхронными событиями таймера, генерируемыми такими классами, как System.Timers.Timer и System.Threading.Timer. Проблема заключается в реентрантности кода (вложенный запуск подпрограмм, reentrancy). Если код Вашего обработчика события таймера занимает для своего выполнения больше времени, чем интервал, с которым таймер генерирует события, и Вы не предприняли необходимые меры предосторожности защиты многопоточного доступа к Вашим объектам, то тогда можете столкнуться с весьма сложными проблемами в отладке. Взгляните на следующий фрагмент кода:
private int tickCounter = 0;
private void tmrTimersTimer_Elapsed (object sender,
System.Timers.ElapsedEventArgs e)
{
System.Threading.Interlocked.Increment(ref tickCounter);
Thread.Sleep(5000);
MessageBox.Show(tickCounter.ToString());
}
Предположим, что свойство Interval таймера было установлено на 1000 миллисекунд, и Вы возможно будете удивлены, что первый всплывший message box покажет значение 5. Это произошло потому, что во время 5 секунд, когда событие первого таймера спало, таймер продолжал генерировать события Elapsed на других рабочих потоках (worker threads). Таким образом, значение переменной tickCounter было инкрементировано 5 раз до того, как было завершена обработка первого события таймера. Обратите внимание, как автор использовал метод Interlocked.Increment для инкремента переменной tickCounter способом, безопасным для следы многопоточного выполнения. Есть и другие способы сделать то же самое, но метод Interlocked.Increment был специально разработан для такого рода операций.
Один из простых способов разрешить проблему реентантности такого типа - обернуть обработчик прерывания таймера в блок кода, который временно запрещает и затем разрешает таймер, как показано в следующем примере:
private void tmrTimersTimer_Elapsed (object sender,
System.Timers.ElapsedEventArgs e)
{
tmrTimersTimer.Enabled = false;
System.Threading.Interlocked.Increment(ref tickCounter);
Thread.Sleep(5000);
MessageBox.Show(tickCounter.ToString());
tmrTimersTimer.Enabled = true;
}
В этом примере кода message box будет появляться каждые 5 секунд, и как Вы можете ожидать, значение tickCounter будет инкрементироваться один раз на одно появление окна message box. Другая возможность - использование примитива синхронизации, такого как Monitor или mutex, чтобы гарантировать, что все будущие события, будут поставлены в очередь, пока текущий обработчик не завершил свое выполнение.
[Заключение]
Чтобы получить быстрый обзор на 3 класса таймеров, доступных в .NET Framework и описанных в этой статье, и сравнить их, посмотрите таблицу ниже. При решении вопроса использования таймера задумайтесь над тем, может ли Ваша проблема быть решена с помощью Планировщика Windows (Windows Scheduler) или команды AT (которая делает то же самое), что дает возможность периодического запуска стандартного выполняемого файла.
Таблица 1. Классы таймеров в .NET FCL.
Вопросы использования таймеров |
System.Windows.Forms |
System.Timers |
System.Threading |
В контексте какого потока запускаются события таймера? |
поток UI (окно формы) |
поток UI или Worker thread |
Worker thread |
Экземпляры класса таймера потокобезопасны? |
нет |
да |
нет |
Понятная/интуитивная объектная модель? |
да |
да |
нет |
Требуется наличие форм (Windows Forms)? |
да |
нет |
нет |
Качество срабатывания тиков как у метронома? |
нет |
да* |
да* |
Событие таймера поддерживает объект state? |
нет |
нет |
да |
Может ли быть запланирован запуск первого события таймера? |
нет |
нет |
да |
Поддерживает ли класс наследование (inheritance)? |
да |
да |
нет |
Примечание *: в зависимости от доступности системных ресурсов (например, worker threads).
[Ссылки]
1. Comparing the Timer Classes in the .NET Framework Class Library (статья в журнале MSDN, февраль 2004 года, автор Alex Calvo, acalvo@hotmail.com) 2. 161017TimersinNET.zip. 3. Windows Forms: Give Your .NET-Based Application a Fast and Responsive UI with Multiple Threads site:microsoft.com. 4. Programming the Thread Pool in the .NET Framework: Using Timers .NET Framework Class Library site:cnblogs.com. |