System timers timer windows forms timer

C# Timer class is a .NET class that helps you create an event that will happen at a set interval. The interface makes it easy to start and stop a timer or enable and disable it.

Timers are very useful in C# for UI programming, games, and applications have logic based on time intervals.

Timers that C# has available through .NET make it easy to time intervals and also to perform specific tasks at specific intervals.

The C# Timer class is a .NET class that helps you create an event that will happen at a set interval. The interface makes it easy to start and stop a timer or enable and disable it.

Timer Usage

The key terms when using the timer class are:

  • Interval: Time interval between two successive invocations of Elapsed function.
  • Callback: The timer invokes this method at the end.
  • AutoReset: Boolean that determines whether the timer will raise the Tick event each time the specified interval has elapsed.

To use a timer in C#, follow these steps:

  1. Setup the timer with your desired interval in milliseconds.

  2. Define the Elapsed event handler. The timer will call this handler every time the interval milliseconds have elapsed.

  3. Setup the timer to call your event handler:

  4. Call the Start() method to start the timer.

  5. Once done with the timer, call the Dispose() method to free up the resources used by the Timer.

Putting it all together:

Plus equal event

The += (plus equal) syntax in C# is a shortcut for defining an event handler. It tells the timer to subscribe to the event handler.

We can also use -= (minus equal) to unsubscribe if needed.

Interval limitation

The C# Timer depends on the system clock.

The resolution of the system clock is how often the Elapsed event will fire. If you set the Interval property to be less than resolving the system clock, then it won’t fire at the desired interval.

For most use cases that won’t be a problem. But if you are looking for high-resolution timer, then consider using the Stopwatch class.

Disposing

Timer class implements System.IDisposable interface to release the system resources allocated by it when disposed.

Best practices when working in C# say that you should clean up your resources if the class implements IDisposable interface.

There are two ways to dispose the Timer:

  • Dispose the timer by calling the Dispose() method inside a try/catch block to avoid exception.
  • Use the using directive to automatically call the Dispose method when a particular scope is clean up. This will avoid the extra try/catch block and code lines to clean up properly.
Microsoft documentation showing a note to dispose IDisposable objects.
Microsoft says to dispose IDisposable objects.

Async timer

The System.Timers.Timer class support async event handler to fire the event at the desired time interval. To define an async event handler, use the async modifier before your signature for Elapsed event handler.

Example:

Keep in mind that the async callback must return a Task. If the async method runs longer than the interval, you might have some unexpected results.

→ Read more: C# Async vs sync

Different timers in .NET

In .NET, there are 4 different timers depending on the way you want to use them:

  • System.Windows.Forms.Timer
  • The System.Web.UI.Timer
  • System.Timers.Timer
  • System.Threading.Timer

System.Windows.Forms.Timer

The System.Windows.Forms.Timer class is specifically designed for rich client user interfaces.

Programmers can drag it into a form as a nonvisual control and regulate the behavior from within the Properties window. It will always safely fire an event from a thread that can interact with the user interface.

System.Web.UI.Timer

The System.Web.UI.Timer can perform Web page post backs at a defined interval, either asynchronously or synchronously. It’s part of the ASP.NET framework, so we can use it only in ASP.NET Web applications.

This timer is useful for creating a real-time display of information on an ASP.NET application.

System.Threading.Timer

The System.Threading.Timer provides the ability to schedule a single callback on a background thread, either asynchronously or synchronously.

System.Threading.Timer is thread-safe.

System.Timers.Timer

System.Timers.Timer is the best way to implement a timer in C#.

System.Timers.Timer is a wrapper for System.Threading.Timer, abstracting and layering on functionality.

You cannot use System.Threading.Timer as a component within a component container, something that implements System.ComponentModel.IContainer, because it does not derive from System.ComponentModel.Component.

System.Timers.Timer extends the capabilities of the System.Timers.Timer to include features necessary for component container applications.

System.Timers.Timer and System.Threading.Timer are both designed for use in server-type processes, but System.Timers.Timer includes a synchronization object to allow it to interact with the user interface, which is helpful for applications that need to keep track of user input or updates.

When to use which timer?

Follow these guidelines to choose which one to use:

  • System.Windows.Forms.Timer — For Windows Forms application, runs the delegate on a form’s UI thread.
  • System.Web.UI.Timer — For ASP.NET component. It allows you to perform asynchronous or synchronous web page post backs at regular intervals.
  • System.Threading.Timer — great for background tasks on a thread pool.
  • System.Timers.Timer — wraps the System.Threading.Timer with the simpler API. We use this one most of the time.

C# Timer Accuracy

The C# Timer is not accurate. The timer, on average, takes 9.07% longer than it is supposed to, according to research from Lawrence Technological University.

To get accurate time, we need to use the .NET StopWatch class.

System.Diagnostics.Stopwatch

The stopwatch class is used to time code execution. It can be started, stopped/paused, and reset.

The main difference between C# Stopwatch and C# Timers is that while the Timer triggers an event when a certain interval has elapsed, Stopwatch calculates how much time has passed since it started. Moreover, the Stopwatch is much more accurate and can measure time to a greater level of precision.

If you want a high-resolution timer, consider using the Stopwatch instead of the Timer.

Stopwatch implementation source code from .NET framework.
Stopwatch implementation source code.

EventHandler vs ElapsedEventHandler

The main difference between EventHandler and ElapsedEventHandler is that EventHandler is more generic and ElapsedEventHandler works with a delegate that has a specific signature.

ElapsedEventHandler is a delegate that passes ElapsedEventArgs which extends EventHandler’s arguments by adding FileTime and SignalTime. The added logic makes the ElapsedEventHandler delegate compatible with ElapsedEventArgs.

ElapsedEventArgs implementation source code from .NET framework.
ElapsedEventArgs implementation source code.

FAQ

What is an event in C#?

An event in C# is a way for a class to let clients know when something interesting happens with an object. We declare events using delegates.

Terms subscribe and unsubscribe are sometimes used to show that a class is interested in a particular event and wants to know when it occurs.

We often don’t need event arguments, so we can implement that as an optional argument in the event handler.

How to name events in C#?

The naming convention suggest naming events in the past tense. For example, OrderPlaced, OrderShipped, etc.

The convention also suggest naming event handler as a combination of event name and EventHandler suffix. For the events above, the event handler would be named OrderPlacedEventHandler, OrderShippedEventHandler.

Conclusion

Timers are a great way to handle time-based operations in C#. They can be used for everything from UI programming to game logic and more.

The timer class is easy to use, and makes it simple to set up an event that will happen at a specific interval.

In order to use a timer in C#, you must first setup the timer with your desired interval and then define the Elapsed event handler. Once the timer is started, it will call the event handler every time the specified interval has elapsed.

If you need a high-resolution timer, consider using the Stopwatch class. When you’re done using the timer, be sure to dispose of it properly by calling the Dispose() method.

The .NET provides a variety of timers to suit your needs, depending on how you want to use them. System.Windows.Forms.Timer is specifically designed for rich client user interfaces, and can be regulated from within the Properties window.  System.Web.UI.Timer can be used in ASP.NET Web applications to create real-time displays of information, while System.Threading.Timer is great for background tasks on a thread pool.

Table of Contents

  • Introduction
  • What is timer?
  • Type of Timer in .NET Framework
  • Schedule a Task
    • Basic Functions
    • Code Usage
    • Output
    • Explanation
  • Summary
  • Reference
  • See Also
  • Download

Introduction

In this article we will see how to use Timer class in .NET Framework under System.Timers namespace. The example is made of C# use under Console Application. Often we need to schedule our task like need for an event to be triggered at various absolute time.
For example every 24 hour, everyday at certain time etc. We can achieve this in various way like using Windows Task Scheduler. But here we will see how we can use Timer class in .NET Framework to achieve this scheduled job. This sample illustrates a way to
let user to know how they can schedule a task using timer.

 Return to Top


What is timer?

According to MSDN
documentation Generates an event after a set interval, with an option to generate recurring events. So, the Timer allows us to set a time interval to periodically execute an event at a specified interval. It is useful when we want to execute
certain functions/applications after a certain interval.

The C# timer event keeps track of time just like a clock would, it is basically an accessible clock and counts in milliseconds, thousandths of a second. This allows for great detail.

Code structure inside the timer:

private
void
timer1_Tick(
object
sender, EventArgs e)

{

  //events occur when timer stops

  timer.Stop();

  Console.WriteLine("Hello World!!!");
//Code to Perform Task goes in between here

  timer.Start();

}

 Return to Top


Type of Timer in .NET Framework

The .NET Framework Class Library provides following different timer classes:

  • System.Windows.Forms.Timer (Windows-based timer)
  • System.Timers.Timer (Server-based timer)
  • System.Threading.Timer (Thread timer)
  • System.Web.UI.Timer (Web-based timer)
  • System.Windows.Threading.DispatcherTimer (Thread timer)

Each of these classes has been designed and optimized for use in different situations. This article describes
System.Timers.Timer class and helps you gain an understanding of how this class should be used. To know more about other timer class please look into

here.

 Return to Top


Schedule a Task

Scheduling a task in code this term means any code that does something, causes something to happen, and has action to it. Example:

  • Making an object appear
  • Making an object move
  • Fire any event
  • Trigger functions

Timer counts automatically. When the timer counts down the amount of time set in the preferences, it executes whatever code is in it, then it automatically restarts and counts down again.

Basic Functions

Some of the functions that are used in this project.

  • Timer.Enabled: «Whether the Timer should raise the Elapsed event.» This must set this to true if we want timer to do anything.
  • Timer.Interval: Interval of the time is count in milliseconds, between raisings of the Elapsed event. The default is 100 milliseconds.» We must make this interval longer than the default. For example, for 60 seconds or 1 minute, use 60000
    as the Interval.
  • Timer.Start: This does the same thing as setting Enabled to true. Starts raising the Elapsed event by setting Enabled to true.
  • Timer.Stop: This does the same thing as setting Enabled to false. Stops raising the Elapsed event by setting Enabled to false.
  • Timer.Tick: Occurs when the specified timer interval has elapsed and the timer is enabled.
  • Timer.Elapsed: This is the event that is invoked each time the Interval of the Timer has passed.

Code Usage

using
System;

using
System.Timers;

namespace
ScheduleTimer

{

    class
Program

    {

        static
Timer timer;

        static
void
Main(
string[] args)

        {

            schedule_Timer();

            Console.ReadLine();

        }

        static
void
schedule_Timer()

        {

            Console.WriteLine("### Timer Started ###");

            DateTime nowTime = DateTime.Now;

            DateTime scheduledTime =
new
DateTime(nowTime.Year, nowTime.Month, nowTime.Day, 8, 42, 0, 0);
//Specify your scheduled time HH,MM,SS [8am and 42 minutes]

            if
(nowTime > scheduledTime)

            {

                scheduledTime = scheduledTime.AddDays(1);

            }

            double
tickTime = (double)(scheduledTime - DateTime.Now).TotalMilliseconds;

            timer =
new
Timer(tickTime);

            timer.Elapsed +=
new
ElapsedEventHandler(timer_Elapsed);

            timer.Start();

        }

        static
void
timer_Elapsed(
object
sender, ElapsedEventArgs e)

        {

            Console.WriteLine("### Timer Stopped ### n");

            timer.Stop();

            Console.WriteLine("### Scheduled Task Started ### nn");

            Console.WriteLine("Hello World!!! - Performing scheduled taskn");

            Console.WriteLine("### Task Finished ### nn");

            schedule_Timer();

        }

    }

}

Output

Explanation

As you can see when the timer hit the specified interval it stops and starts performing the given task, for our case just simply print «Hello World» on the console window.

If we look closer we can see that a timer object is created, then we assigned the interval in millisecond by calculating current time with scheduled time. Then we assign the associated method the timer will execute after interval.

Then Start method is called to start the interval. The timer continues to operate until it hit the specified interval. When it hit the specified interval Stop method is called to stop the timer, perform the specified task, after completing start the timer again.

Two DateTime object is created. Once is for the current date time and other is for to detecting scheduled date time. Also our code has the ability to automatically increment it’s date to next day to perform the task daily. So, no user interaction needed.

 Return to Top


Summary

We looked at the Timer class from the System.Timers namespace in .NET Framework. One point we may want to consider when working with timers is whether our problem can be solved more simply by using the Windows Scheduler to run a standard executable
periodically. Here timer is used to fires off a program at a given time. We could also use timer in following cases:

  • Fire off a program at a given time
  • Display the time on the screen
  • Create a backup routine that copies important data at a given interval
  • Can create a routine to automatically log off a user or end a program after a given time period with no activity
  • Create a WCF service add timer to schedule a job

Beside these numerous ways timer can be used.

 Return to Top


Reference

  • Timer class under System.Timers namespace
    here
  • All about .NET Timers — A Comparison
    here
  • Introduction to Server-Based Timers
    here
  • Timers, Timer Resolution, and Development of Efficient Code
    here

 Return to Top


See Also

  • Comparing
    the Timer Classes in the .NET Framework Class Library
  • Quartz
    Enterprise Scheduler .NET
  • Limitations
    of the Windows Forms Timer Component’s Interval Property

 Return to Top


Download

You can download the Source Code used in the example from this link
Download Source Code

 Return to Top


web analytics

Вы пишите код на платформе .NET под Windows и вам нужно выполнять некоторые действия каждую миллисекунду. Возможно ли это? Какие есть варианты и насколько они надёжны? Разберёмся, что можно использовать, и какие есть гарантии по точности срабатывания. Статья сконцентрирована на поиске такого решения, которое работало бы и под .NET Framework, и под .NET Core / .NET, и в разных версиях ОС, и являлось бы механизмом общего назначения (а не только для программ с GUI, например).

Для чего вообще может потребоваться таймер с малым периодом? Примером могут служить различные программные аудио- и видеоплееры. Классический подход при воспроизведении мультимедийных данных – раз в N единиц времени смотреть, что́ нужно подать на устройство вывода (видео-, звуковую карту и т.д.) в данный момент времени, и при необходимости отсылать новые данные (кадр, аудиобуфер) на это устройство. В таких случаях информация часто расположена достаточно плотно (особенно в случае с аудио), а временны́е отклонения в её воспроизведении хорошо заметны ушам, глазам и прочим человеческим органам. Поэтому N выбирается небольшим, измеряется в миллисекундах, и часто используется значение 1.

Я разрабатываю библиотеку для работы с MIDI – DryWetMIDI. Помимо взаимодействия с MIDI файлами, их трансформации и сопряжения с музыкальной теорией, библиотека предлагает API для работы с MIDI устройствами, а также средства для воспроизведения и записи MIDI данных. DryWetMIDI написана на C#, а мультимедийный API реализован для Windows и macOS. Вкратце воспроизведение в библиотеке работает так:

  1. все MIDI-события снабжаются временем, когда они должны быть воспроизведены, время измеряется в миллисекундах и отсчитывается от начала всех данных (т.е. от 0);

  2. указатель P устанавливается на первое событие;

  3. запускается счётчик времени C;

  4. запускается таймер T с интервалом 1 мс;

  5. при каждом срабатывании 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 PathDeviceHarddiskVolume3Program Files (x86)GoogleChromeApplicationchrome.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 PathDeviceHarddiskVolume3Program Files (x86)Windows Media Playerwmplayer.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 PathDeviceHarddiskVolume3Program FilesVideoLANVLCvlc.exe

Есть подозрение (непроверенное), что частота таймера зависит от частоты кадров видео. А быть может, разработчики видеоплеера просто посчитали наглостью всегда запрашивать 1 мс. И, возможно, они правы.

Подготовка тестового кода

Создадим каркас наших тестов. Опишем интерфейс таймера:

using System;

namespace Common
{
    public interface ITimer
    {
        void Start(int intervalMs, Action callback);
        void Stop();
    }
}

Метод Start принимает первым параметром интервал таймера. Я решил проверить работу таймеров не только для интервала 1 мс, но также и для 10 и 100 мс. Вторым параметром будем передавать метод, который будет выполняться при срабатывании таймера.

Все наши проверки сделаем в одном классе:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace Common
{
    public static class TimerChecker
    {
        private static readonly TimeSpan MeasurementDuration = TimeSpan.FromMinutes(3);
        private static readonly int[] IntervalsToCheck = { 1, 10, 100 };

        public static void Check(ITimer timer)
        {
            Console.WriteLine("Starting measuring...");
            Console.WriteLine($"OS: {Environment.OSVersion}");
            Console.WriteLine("--------------------------------");

            foreach (var intervalMs in IntervalsToCheck)
            {
                Console.WriteLine($"Measuring interval of {intervalMs} ms...");
                MeasureInterval(timer, intervalMs);
            }

            Console.WriteLine("All done.");
        }

        private static void MeasureInterval(ITimer timer, int intervalMs)
        {
            var times = new List<long>((int)Math.Round(MeasurementDuration.TotalMilliseconds) + 1);
            var stopwatch = new Stopwatch();
            Action callback = () => times.Add(stopwatch.ElapsedMilliseconds);

            timer.Start(intervalMs, callback);
            stopwatch.Start();

            Thread.Sleep(MeasurementDuration);

            timer.Stop();
            stopwatch.Stop();

            var deltas = new List<long>();
            var lastTime = 0L;

            foreach (var time in times.ToArray())
            {
                var delta = time - lastTime;
                deltas.Add(delta);
                lastTime = time;
            }

            File.WriteAllLines($"deltas_{intervalMs}.txt", deltas.Select(d => d.ToString()));
        }
    }
}

Т.е.

  1. запускаем Stopwatch;

  2. в течение 3 минут складываем с него время при каждом срабатывании таймера в список;

  3. собираем интервалы между собранными временами;

  4. записываем полученные дельты в текстовый файл deltas_<interval>.txt.

Далее по этим наборам дельт строим графики, чтобы наглядно видеть, насколько точно срабатывает таймер. Содержимое каждого графика будет таким:

Типичный график интервалов между срабатываниями таймера

Типичный график интервалов между срабатываниями таймера

Справа сверху будет отображаться процент “хороших” результатов – дельт, попадающих в 10-процентную окрестность вокруг заданного интервала. Число 10 выбрано навскидку, но, как мы увидим, оно вполне помогает понять разницу между таймерами.

Если не сказано явно, запуск тестов производится на виртуальных машинах Azure Pipelines из пула Microsoft с операционной системой Microsoft Windows Server 2019 (10.0.17763). Иногда будем смотреть на моей локальной машине с ОС Windows 10 20H2 (сборка 19042.1348). Windows 11 под рукой нет, быть может, кому-то будет интересно проверить там.

Я решил сделать тест каждого варианта в виде отдельного консольного приложения. Все эти приложения собрал вместе в солюшне в виде проектов. Все ссылки на код, данные и графики будут приведены в конце статьи. А мы начинаем наше исследование.

EDIT ────────

В комментариях было высказано много интересных идей и предложений. Не все из них меняют итоги статьи, но для полноты картины упомянуть стоит. Кроме того, будут добавлены графики загрузки процессора. Такие вставки будут оформлены, как этот абзац.

────────────

Бесконечный цикл

Нельзя обойти стороной наивный подход – таймер на основе бесконечного цикла с подсчётом интервала:

using Common;
using System;
using System.Diagnostics;
using System.Threading;

namespace InfiniteLoopTimer
{
    internal sealed class Timer : ITimer
    {
        private bool _running;

        public void Start(int intervalMs, Action callback)
        {
            var thread = new Thread(() =>
            {
                var lastTime = 0L;
                var stopwatch = new Stopwatch();

                _running = true;
                stopwatch.Start();

                while (_running)
                {
                    if (stopwatch.ElapsedMilliseconds - lastTime < intervalMs)
                        continue;

                    callback();
                    lastTime = stopwatch.ElapsedMilliseconds;
                }
            });

            thread.Start();
        }

        public void Stop()
        {
            _running = false;
        }
    }
}

Запустив тест с этим таймером

using Common;

namespace InfiniteLoopTimer
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TimerChecker.Check(new Timer());
        }
    }
}

получим, разумеется, отличные результаты. Например, для 1 мс:

1 мс

1 мс

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

Загрузка процессора на бесконечном цикле

Загрузка процессора на бесконечном цикле

То есть примерно одно ядро. Графики для других интервалов приводить не буду, там аналогичные картины (кто хочет, может посмотреть по ссылке в конце статьи).

EDIT ────────

Как ни странно, именно бесконечный цикл вызвал в комментариях наибольшую активность. Было предложено несколько вариантов, рассмотрим каждый.

Во-первых, не один человек уверенно высказался о том, что вызов метода Thread.Yield должен снизить загрузку процессора. Что ж, напишем новый таймер:

using System;
using System.Diagnostics;
using System.Threading;
using Common;

namespace InfiniteLoopTimerWithThreadYield
{
    internal sealed class Timer : ITimer
    {
        private bool _running;

        public void Start(int intervalMs, Action callback)
        {
            var thread = new Thread(() =>
            {
                var lastTime = 0L;
                var stopwatch = new Stopwatch();

                _running = true;
                stopwatch.Start();

                while (_running)
                {
                    if (stopwatch.ElapsedMilliseconds - lastTime >= intervalMs)
                    {
                        callback();
                        lastTime = stopwatch.ElapsedMilliseconds;
                    }

                    if (!Thread.Yield())
                        Thread.Sleep(0);
                }
            });

            thread.Start();
        }

        public void Stop()
        {
            _running = false;
        }
    }
}

Точность будет высокая (смотрел на своём локальном компьютере)

1 мс

1 мс

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

Загрузка CPU

Загрузка CPU

Результаты аналогичные и для виртуалок Azure Pipelines и для второго компьютера. Т.е. совершенно точно нельзя назвать такой таймер хорошим.

Во-вторых, в комментариях были упомянуты NtSetTimerResolution / NtDelayExecution. Это недокументированные функции системной библиотеки ntdll.dll. Я модифицировал бесконечный цикл простейшим образом с использованием этих функций, сделав такой таймер:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using Common;

namespace InfiniteLoopTimerWithNtDelayExecution
{
    internal sealed class Timer : ITimer
    {
        [DllImport("ntdll.dll", SetLastError = true)]
        private static extern void NtSetTimerResolution(uint DesiredResolution, bool SetResolution, ref uint CurrentResolution);

        [DllImport("ntdll.dll")]
        private static extern bool NtDelayExecution(bool Alertable, ref long DelayInterval);

        private Thread _thread;
        private bool _running;

        public void Start(int intervalMs, Action callback)
        {
            var res = (uint)(intervalMs * 10000);
            NtSetTimerResolution(res, true, ref res);

            _thread = new Thread(() =>
            {
                _running = true;

                while (_running)
                {
                    var interval = -intervalMs * 10000L;
                    NtDelayExecution(false, ref interval);
                    callback();
                }
            }) { Priority = ThreadPriority.Highest };

            _thread.Start();
        }

        public void Stop()
        {
            _running = false;
        }
    }
}

Такая реализация обладает некоторыми недостатками (например тем, что срабатывания будут «плыть» в зависимости от времени выполнения callback), но для демонстрации вполне годится. Запустив, получим такие результаты на локальной машине:

1 мс

1 мс
10 мс
10 мс

На виртуалках Azure Pipelines результаты тоже отличные, хотя среднее значение и не совпадает с запрошенным интервалом. Например, для интервала 1 мс:

1 мс

1 мс

То бишь такой таймер действительно неплох.

────────────

Переходим к стандартным классам таймеров в .NET.

System.Timers.Timer

Используя System.Timers.Timer

using Common;
using System;

namespace SystemTimersTimer
{
    internal sealed class Timer : ITimer
    {
        private System.Timers.Timer _timer;

        public void Start(int intervalMs, Action callback)
        {
            _timer = new System.Timers.Timer(intervalMs);
            _timer.Elapsed += (_, __) => callback();
            _timer.Start();
        }

        public void Stop()
        {
            _timer.Stop();
        }
    }
}

получим такие результаты:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

EDIT ────────

Загрузка CPU, разумеется околонулевая, ибо таймер работает на пуле потоков:

1 мс

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

Код нового таймера:

using Common;
using System;

namespace SystemTimersTimerWithPeriod
{
    internal sealed class Timer : ITimer
    {
        private System.Timers.Timer _timer;
        private uint _resolution;

        public void Start(int intervalMs, Action callback)
        {
            _timer = new System.Timers.Timer(intervalMs);
            _timer.Elapsed += (_, __) => callback();

            _resolution = NativeTimeApi.BeginPeriod(intervalMs);
            _timer.Start();
        }

        public void Stop()
        {
            _timer.Stop();
            NativeTimeApi.EndPeriod(_resolution);
        }
    }
}

Не буду здесь приводить код класса NativeTimeApi, кому интересно, посмотрит его в архиве с солюшном (ссылка в конце статьи). Запускаем тест:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Увы, лучше не стало. Если немного погуглить, обнаружим, что мы не одиноки в своём горе:

  • timeBeginPeriod not working on Intel Comet Lake CPU (i5 10400H)

  • The timeBeginPeriod() function no longer changes the resolution of SetThreadpoolTimer() and CreateTimerQueueTimer() in Windows 10 2004

  • WIndows 10 timeBeginPeriod(1) not always working

Оказывается, начиная с версии Windows 10 2004 изменилось влияние функции timeBeginPeriod на стандартные таймеры. А именно, теперь она на них не влияет. По этой теме можно почитать интересную статью – Windows Timer Resolution: The Great Rule Change. К слову, выглядит, что проблема присутствует и на более ранних версиях Windows 10.

Кстати говоря, визуально результаты всё же стали немного другими. А именно, уменьшился разброс относительно среднего значения. Это может быть случайностью, а может быть и влиянием функции timeBeginPeriod.

EDIT ────────

Загрузка процессора:

1 мс

1 мс

────────────

System.Threading.Timer

Для полноты картины нужно также посмотреть, а как обстоят дела с System.Threading.Timer. Код:

using Common;
using System;

namespace SystemThreadingTimer
{
    internal sealed class Timer : ITimer
    {
        private System.Threading.Timer _timer;

        public void Start(int intervalMs, Action callback)
        {
            _timer = new System.Threading.Timer(_ => callback(), null, intervalMs, intervalMs);
        }

        public void Stop()
        {
            _timer.Dispose();
        }
    }
}

Результаты:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

EDIT ────────

Процессор нагружен аналогично предыдущему таймеру:

1 мс

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 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Не сработало.

EDIT ────────

Загрузка процессора:

1 мс

1 мс

────────────

Multimedia timer

В Windows издревле существует API для создания мультимедийных таймеров. Использование их состоит в регистрации функции обратного вызова с помощью timeSetEvent и предварительном вызове timeBeginPeriod. Таким образом, опишем новый таймер:

using Common;
using System;

namespace WinMmTimer
{
    internal sealed class Timer : ITimer
    {
        private uint _resolution;
        private NativeTimeApi.TimeProc _timeProc;
        private Action _callback;
        private uint _timerId;

        public void Start(int intervalMs, Action callback)
        {
            _callback = callback;

            _resolution = NativeTimeApi.BeginPeriod(intervalMs);
            _timeProc = TimeProc;
            _timerId = NativeTimeApi.timeSetEvent((uint)intervalMs, _resolution, _timeProc, IntPtr.Zero, NativeTimeApi.TIME_PERIODIC);
        }

        public void Stop()
        {
            NativeTimeApi.timeKillEvent(_timerId);
            NativeTimeApi.EndPeriod(_resolution);
        }

        private void TimeProc(uint uID, uint uMsg, uint dwUser, uint dw1, uint dw2)
        {
            _callback();
        }
    }
}

Запустив тест, получим такие результаты:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

А вот это уже интересно. Проверим на локальной машине:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Тут вообще красота. Таймер прекрасно держит заданный интервал, лишь изредка заметно отклоняясь от него (чего избежать невозможно на Windows, ибо эта ОС не является системой реального времени).

EDIT ────────

Дополним результаты графиками загрузки процессора. На локальной машине:

1 мс

1 мс

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

1 мс

1 мс

Нагрузка на процессор минимальная.

────────────

Итак, результаты радуют. Однако, в документации сказано, что функция timeSetEvent устаревшая:

This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.

Вместо неё предлагается использовать функцию CreateTimerQueueTimer. Что ж, мы, как законопослушные разработчики идём пробовать.

Timer-queue timer

Вместо мультимедийных таймеров Microsoft рекомендует использовать таймеры, созданные на специальных очередях. Можно использовать дефолтную очередь, а можно и создавать свои. Мы будем использовать дефолтную. Код нашего таймера:

using Common;
using System;

namespace TimerQueueTimerUsingDefault
{
    internal sealed class Timer : ITimer
    {
        private IntPtr _timer;
        private NativeTimeApi.WaitOrTimerCallback _waitOrTimerCallback;
        private Action _callback;

        public void Start(int intervalMs, Action callback)
        {
            _callback = callback;
            _waitOrTimerCallback = WaitOrTimerCallback;

            NativeTimeApi.CreateTimerQueueTimer(
                ref _timer,
                IntPtr.Zero,
                _waitOrTimerCallback,
                IntPtr.Zero,
                (uint)intervalMs,
                (uint)intervalMs,
                NativeTimeApi.WT_EXECUTEDEFAULT);
        }

        public void Stop()
        {
            NativeTimeApi.DeleteTimerQueueTimer(IntPtr.Zero, _timer, IntPtr.Zero);
        }

        private void WaitOrTimerCallback(IntPtr lpParameter, bool TimerOrWaitFired)
        {
            _callback();
        }
    }
}

Здесь в параметр Flags функции CreateTimerQueueTimer мы передаём WT_EXECUTEDEFAULT. Чуть позже посмотрим и на другой флаг. А пока запустим тест:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Выглядит многообещающе. Проверим на локальной машине:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Как ни странно, в разных версиях Windows таймер работает по-разному. На моей Windows 10 результаты не лучше стандартных .NET таймеров.

EDIT ────────

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

1 мс

1 мс

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

1 мс

1 мс

Процессор нагружается одинаково при том, что точность разная. Не буду приводить графики загрузки CPU в дальнейших разделах, там всё то же самое.

────────────

Timer-queue timer + timeBeginPeriod

Интереса ради я проверил предыдущий таймер с предварительной установкой периода системного таймера на локальной машине:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Внезапно на 10 мс неплохие результаты. Но для 1 мс всё так же плохо.

Timer-queue timer + WT_EXECUTEINTIMERTHREAD

В прошлый раз мы использовали опцию WT_EXECUTEDEFAULT при создании таймера. Попробуем установить другую – WT_EXECUTEINTIMERTHREAD. Результаты (по-прежнему используем локальную машину):

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

И хотя ничего нового, любопытно, что у таймеров на очередях очень малый разброс значений. Практически все дельты попадают в чёткий диапазон.

Timer-queue timer + WT_EXECUTEINTIMERTHREAD + timeBeginPeriod

Без лишних слов:

1 мс

1 мс
10 мс
10 мс
100 мс
100 мс

Глядя на графики, я всё-таки прихожу к выводу, что timeBeginPeriod как-то да влияет на таймеры. Коридор значений для интервала 1 мс явно становится уже.

Итоги

Буду честен, рассмотрены не все варианты. Вот тут в блоке Tip перечислены ещё такие:

  • System.Windows.Forms.Timer;

  • System.Web.UI.Timer;

  • System.Windows.Threading.DispatcherTimer.

Но и это ещё не всё. В .NET 6 появился PeriodicTimer. Зоопарк разных таймеров в .NET и Windows, конечно, весьма солидный.

Но все эти таймеры не подходят. Как я писал до ката: статья сконцентрирована на поиске такого решения, которое работало бы и под .NET Framework, и под .NET Core / .NET, и в разных версиях ОС, и являлось бы механизмом общего назначения. А потому вот причины отказа от упомянутых классов (по крайней мере для нужд мультимедиа):

  • System.Windows.Forms.Timer – выполняется на UI-потоке, привязка к приложениям с GUI;

  • System.Web.UI.Timer – только .NET Framework, да и Web.UI в имени пространства имён не вяжется с решением общего назначения;

  • System.Windows.Threading.DispatcherTimer – для WPF;

  • PeriodicTimer – только с .NET 6.

А что же можно сказать о тех таймерах, что были проверены в статье? Нет смысла писать много букв, единственный надёжный вариант – мультимедийные таймеры. И хотя они давно объявлены устаревшими, только они соответствуют критериям, указанным до ката.

EDIT ────────

Видя результаты работы таймера, основанного на функциях NtSetTimerResolution / NtDelayExecution должен признать, что это также отличный вариант достичь точности в 1 мс. Более того, таким образом можно достичь и большей точности, чего невозможно сделать с мультимедийными таймерами. Большое спасибо @Alexx999 и всем неравнодушным к теме!

────────────

Всем спасибо. Как и обещал, привожу ссылки:

  • код солюшна с тестовым кодом;

  • результаты с виртуальных машин Azure Pipelines;

  • результаты с локальной машины;

  • графики по результатам с виртуальных машин Azure Pipelines;

  • графики по результатам с локальной машины.

Синтаксис

  • myTimer.Interval — устанавливает, как часто вызывается событие «Tick» (в миллисекундах)
  • myTimer.Enabled — логическое значение, которое устанавливает таймер для включения / отключения
  • myTimer.Start() — запуск таймера.
  • myTimer.Stop() — останавливает таймер.

замечания

Если вы используете Visual Studio, таймеры могут быть добавлены в виде элемента управления непосредственно в вашу форму из панели инструментов.

Многопоточные таймеры

System.Threading.Timer — Простой многопоточный таймер. Содержит два метода и один конструктор.

Пример: таймер вызывает метод DataWrite, который пишет «многопоточность, выполненный …», по прошествии пяти секунд, а затем каждую секунду после этого, пока пользователь не нажмет Enter:

using System;
using System.Threading;
class Program
{
  static void Main()
  {
    // First interval = 5000ms; subsequent intervals = 1000ms
    Timer timer = new Timer (DataWrite, "multithread executed...", 5000, 1000);
    Console.ReadLine();
    timer.Dispose(); // This both stops the timer and cleans up.
  }

  static void DataWrite (object data)
  {
    // This runs on a pooled thread
    Console.WriteLine (data); // Writes "multithread executed..."
  }
}

Примечание. Будет опубликован отдельный раздел для утилизации многопоточных таймеров.

Change Этот метод можно вызвать, если вы хотите изменить интервал таймера.

Timeout.Infinite — если вы хотите запустить только один раз. Задайте это в последнем аргументе конструктора.

System.Timers — еще один класс таймера, предоставляемый .NET Framework. Он обертывает System.Threading.Timer .

Особенности:

  • IComponentIComponent его размещение в лотке компонента Designer в Visual Studio
  • Свойство Interval вместо метода Change
  • Elapsed event вместо delegate обратного вызова
  • Enabled для запуска и остановки таймера ( default value = false )
  • Start & Stop если вы запутались в свойстве Enabled (выше точки)
  • AutoReset — для указания повторяющегося события ( default value = true )
  • Свойство SynchronizingObject с методами Invoke и BeginInvoke для безопасных методов вызова элементов WPF и элементов управления Windows Forms

Пример, представляющий все перечисленные выше функции:

using System;
using System.Timers; // Timers namespace rather than Threading
class SystemTimer
{
  static void Main()
  {
    Timer timer = new Timer(); // Doesn't require any args
    timer.Interval = 500;
    timer.Elapsed += timer_Elapsed; // Uses an event instead of a delegate
    timer.Start(); // Start the timer
    Console.ReadLine();
    timer.Stop(); // Stop the timer
    Console.ReadLine();
    timer.Start(); // Restart the timer
    Console.ReadLine();
    timer.Dispose(); // Permanently stop the timer
 }

 static void timer_Elapsed(object sender, EventArgs e)
 {
   Console.WriteLine ("Tick");
 }
}

Multithreaded timers — используйте пул потоков, чтобы несколько потоков могли обслуживать множество таймеров. Это означает, что метод обратного вызова или Elapsed событие может запускаться по другому потоку каждый раз, когда он вызывается.

Elapsed — это событие всегда срабатывает вовремя, независимо от того, является ли предыдущим Elapsed закончило событие выполнения. Из-за этого обратные вызовы или обработчики событий должны быть потокобезопасными. Точность многопоточных таймеров зависит от ОС и обычно составляет 10-20 мс.

interop — когда вам нужна более высокая точность, используйте это и вызовите мультимедийный таймер Windows. Это имеет точность до 1 мс и определяется в winmm.dll .

timeBeginPeriod — сначала timeBeginPeriod это, чтобы сообщить ОС, что вам нужна высокая точность синхронизации

timeSetEvent — вызывать это через timeBeginPeriod для запуска мультимедийного таймера.

timeKillEvent — вызывать это, когда вы закончите, это останавливает таймер

timeEndPeriod — вызов этого, чтобы сообщить ОС, что вам больше не нужна высокая точность синхронизации.

Вы можете найти полные примеры в Интернете, которые используют мультимедийный таймер, dllimport поиск ключевых слов dllimport winmm.dll timesetevent .

Создание экземпляра таймера

Таймеры используются для выполнения задач через определенные промежутки времени (до X каждые Y секунд). Ниже приведен пример создания нового экземпляра таймера.

ПРИМЕЧАНИЕ . Это относится к таймерам, использующим WinForms. Если вы используете WPF, вы можете посмотреть в DispatcherTimer

    using System.Windows.Forms; //Timers use the Windows.Forms namespace

    public partial class Form1 : Form
    {

        Timer myTimer = new Timer(); //create an instance of Timer named myTimer

    
        public Form1()
        {
            InitializeComponent();
        }

    }

Назначение обработчика события «Tick» для таймера

Все действия, выполняемые таймером, обрабатываются в событии «Tick».

public partial class Form1 : Form
{

    Timer myTimer = new Timer();

    
    public Form1()
    {
        InitializeComponent();

        myTimer.Tick += myTimer_Tick; //assign the event handler named "myTimer_Tick"
    }

    private void myTimer_Tick(object sender, EventArgs e)
    {
        // Perform your actions here.
    }
}

Пример: использование таймера для простого обратного отсчета.

    public partial class Form1 : Form
    {

    Timer myTimer = new Timer();
    int timeLeft = 10;
    
        public Form1()
        {
            InitializeComponent();

            //set properties for the Timer
            myTimer.Interval = 1000;
            myTimer.Enabled = true;

            //Set the event handler for the timer, named "myTimer_Tick"
            myTimer.Tick += myTimer_Tick;

            //Start the timer as soon as the form is loaded
            myTimer.Start();

            //Show the time set in the "timeLeft" variable
            lblCountDown.Text = timeLeft.ToString();

        }

        private void myTimer_Tick(object sender, EventArgs e)
        {
            //perform these actions at the interval set in the properties.
            lblCountDown.Text = timeLeft.ToString();
            timeLeft -= 1;

            if (timeLeft < 0)
            {
                myTimer.Stop();
            }
        }
    }

Результаты в …

введите описание изображения здесь введите описание изображения здесь

И так далее…

In this article, we are going to learn how to use Timer in C#. We can set a timer to generate events following a previously set interval. In addition, the Timer class does this without blocking our program’s main execution thread.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

How Does Timer Work in C#?

Let’s see an example of how to use a Timer in C#:

public class Program
{
    public static void Main(string[] args)
    {
        var timer = new Timer(2000);
        timer.Elapsed += OnEventExecution;
        timer.Start();

        Console.ReadLine();
    }

    public static void OnEventExecution(Object? sender, ElapsedEventArgs eventArgs)
    {
        Console.WriteLine($"Elapsed event at {eventArgs.SignalTime:G}");
    }
}

Here, we create an instance of the Timer class. We use the constructor to set up a 2000 milliseconds (2 seconds) interval. After that, we use the OnEventExecution static method as our event handler and we assign it to the timer’s Elapsed event. Finally, we call the Start() method so the events start to fire at the defined interval. Another way to start the timer would be to set the Enabled property to true.

On execution, we should see the program printing a new line to the console every 2 seconds:

Elapsed event at 3/19/2022 12:20:11 PM
Elapsed event at 3/19/2022 12:20:13 PM
Elapsed event at 3/19/2022 12:20:15 PM

In other words, our timer raises the elapsed event repeatedly using the interval we set up initially.

If needed, we can stop a timer by calling its Stop() method or by setting its Enabled property to false.

Generating Recurring Events with Timer in C#

Any newly created timer will repeatedly raise events once started. That’s because the timer’s AutoReset property is set to true by default.

However, in a scenario where we only need our timer to raise the Elapsed event once we should set the AutoReset property to false:

var timer = new Timer(2000);
timer.Elapsed += OnEventExecution;
timer.AutoReset = false;  // Disable recurrent events.
timer.Start();

This time the output consists of one line only because the event was triggered once.

Implementing Handlers

So far, we’ve been using explicitly defined methods as event handlers for our timer. However, we have more options to implement our handlers.

Whenever we deal with really simple handlers, we can use lambda syntax to shorten the implementation:

var timer = new Timer(2000);

timer.Elapsed += (sender, eventArgs) =>
{
    Console.WriteLine($"Elapsed event at {eventArgs.SignalTime:G}");
};

timer.Start();

We can implement asynchronous event handlers as well:

var sw = new StringWriter();
var timer = new Timer(2000);

timer.Elapsed += async (sender, eventArgs) =>
{
    await sw.WriteLineAsync($"Elapsed event at {eventArgs.SignalTime:G}");
};

timer.Start();

Exception Handling

Whenever a timer event handler throws an exception, the timer component catches it and suppresses it. For instance, the following code won’t write any exception messages to the console:

var timer = new Timer(2000);

timer.Elapsed += (sender, eventArgs) =>
{
    Console.WriteLine($"Elapsed event at {eventArgs.SignalTime:G}");
    throw new Exception();
};

timer.Start();

Note that we must never rely on this behavior since it is bound to change in future versions of the .NET Framework.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

On the other hand, exceptions raised within asynchronous handlers will not be suppressed by the timer component:

var timer = new Timer(2000);

timer.Elapsed += async (sender, eventArgs) =>
{
    await Task.Run(() => Console.WriteLine($"Elapsed event at {eventArgs.SignalTime:G}"));
    throw new Exception();
};

timer.Start();

This time the program will show the error message and exit.

Disposing Timers

The Timer component implements the IDisposable interface. In most cases, timer instances in our applications will be disposed of automatically when they go out of scope. However, in certain cases, we have to do it manually:

using (var timer = new Timer(2000))
{
    timer.Elapsed += (sender, eventArgs) =>
    {
        Console.WriteLine($"Elapsed event at {eventArgs.SignalTime:G}");
    };

    timer.Start();
    Console.ReadLine();  // Avoids ending the timer's scope too soon
}

We leverage the using statement to dispose of our timer once we are done with it.

Timer and Windows Forms

Usually, Timer calls the Elapsed event handler in the system-thread pool. This may not work when the source of the event is a visual Windows Forms component like a Form, a TextBox, or a Button.

To avoid this pitfall, we must set the SynchronizingObject property to reference the component that handles the event. This way, our Timer will call the event handler in the same thread where the component is located.

Alternatives to Timer in C#

System.Timers and the Timer class have been a part of the framework since version 1.1 so it is available in whichever target framework we work with. However, the framework includes other Timer classes, each one with a different purpose and behavior.

System.Threading.Timer executes a given method a single time after a set interval. The callback method needs to be provided in the constructor and can’t be changed.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

System.Windows.Forms.Timer is a Windows Forms component suited for single-thread environments.

System.Web.UI.Timer is part of ASP.NET and performs page postbacks at regular intervals. Only available in .NET Framework.

Timer vs Stopwatch

Timer executes events in a separate thread at a specific user-defined interval. On the other hand, Stopwatch runs in our program’s main thread and measures the elapsed time. Stopwatch returns a TimeSpan struct containing the elapsed time:

var stopwatch = new Stopwatch();
stopwatch.Start();

Thread.Sleep(100);

stopwatch.Stop();

Console.WriteLine($"Total milliseconds: {stopwatch.Elapsed.TotalMilliseconds:F4}"); // Total milliseconds: 108.8356
Console.WriteLine($"Total seconds: {stopwatch.Elapsed.TotalSeconds:F4}"); // Total seconds: 0.1088
Console.WriteLine($"Total minutes: {stopwatch.Elapsed.TotalMinutes:F4}"); // Total minutes: 0.0018

Although both of these classes work with time, we can clearly see that the StopWatch class has a completely different application.

Third-party Alternatives

Let’s have a look at some free third-party libraries that can replace Timer. In general, these options offer an array of advanced features in exchange for some added complexity.

Quartz.NET works embedded in a program or stand-alone. It offers many ways to schedule jobs beyond a simple time interval setup. It can work in clusters, enabling fail-over and load balancing setups.

Hangfire is a job scheduler that tracks jobs in persistent storage ensuring job execution. It schedules jobs based on intervals, like Timer, but also offers many more options like job queues or batches.

Conclusion

In this article, we’ve learned what Timer in C# is and how it works. We’ve practiced creating timer event handlers and how to dispose of our timer instances.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

We’ve also learned about the differences between Timer and StopWatch and, finally, we had a look at some popular third-party alternatives.

I have been checking out some of the possible timers lately, and System.Threading.Timer and System.Timers.Timer are the ones that look needful to me (since they support thread pooling).

I am making a game, and I plan on using all types of events, with different intervals, etc.

Which would be the best?

Wai Ha Lee's user avatar

Wai Ha Lee

8,41977 gold badges60 silver badges90 bronze badges

asked Sep 13, 2009 at 3:56

TheAJ's user avatar

0

This article offers a fairly comprehensive explanation:

«Comparing the Timer Classes in the .NET Framework Class Library» — also available as a .chm file

The specific difference appears to be that System.Timers.Timer is geared towards multithreaded applications and is therefore thread-safe via its SynchronizationObject property, whereas System.Threading.Timer is ironically not thread-safe out-of-the-box.

I don’t believe that there is a difference between the two as it pertains to how small your intervals can be.

Wai Ha Lee's user avatar

Wai Ha Lee

8,41977 gold badges60 silver badges90 bronze badges

answered Sep 13, 2009 at 3:59

David Andres's user avatar

David AndresDavid Andres

31.1k7 gold badges45 silver badges36 bronze badges

10

System.Threading.Timer is a plain timer. It calls you back on a thread pool thread (from the worker pool).

System.Timers.Timer is a System.ComponentModel.Component that wraps a System.Threading.Timer, and provides some additional features used for dispatching on a particular thread.

System.Windows.Forms.Timer instead wraps a native message-only-HWND and uses Window Timers to raise events in that HWNDs message loop.

If your app has no UI, and you want the most light-weight and general-purpose .Net timer possible, (because you are happy figuring out your own threading/dispatching) then System.Threading.Timer is as good as it gets in the framework.

I’m not fully clear what the supposed ‘not thread safe’ issues with System.Threading.Timer are. Perhaps it is just same as asked in this question: Thread-safety of System.Timers.Timer vs System.Threading.Timer, or perhaps everyone just means that:

  1. it’s easy to write race conditions when you’re using timers. E.g. see this question:
    Timer (System.Threading) thread safety

  2. re-entrancy of timer notifications, where your timer event can trigger and call you back a second time before you finish processing the first event. E.g. see this question: Thread-safe execution using System.Threading.Timer and Monitor

Community's user avatar

answered Jan 28, 2014 at 15:47

Tim Lovell-Smith's user avatar

Tim Lovell-SmithTim Lovell-Smith

14.8k14 gold badges73 silver badges92 bronze badges

2

In his book «CLR Via C#«, Jeff Ritcher discourages using System.Timers.Timer, this timer is derived from System.ComponentModel.Component, allowing it to be used in design surface of Visual Studio. So that it would be only useful if you want a timer on a design surface.

He prefers to use System.Threading.Timer for background tasks on a thread pool thread.

Nate B.'s user avatar

Nate B.

94211 silver badges31 bronze badges

answered Apr 12, 2011 at 10:51

Hero's user avatar

HeroHero

1,4251 gold badge10 silver badges9 bronze badges

4

Information from Microsoft about this (see Remarks on MSDN):

  • System.Timers.Timer,
    which fires an event and executes the code in one or more event sinks
    at regular intervals. The class is intended for use as a server-based
    or service component in a multithreaded environment; it has no user
    interface and is not visible at runtime.
  • System.Threading.Timer,
    which executes a single callback method on a thread pool thread at
    regular intervals. The callback method is defined when the timer is
    instantiated and cannot be changed. Like the System.Timers.Timer
    class, this class is intended for use as a server-based or service
    component in a multithreaded environment; it has no user interface and
    is not visible at runtime.
  • System.Windows.Forms.Timer
    (.NET Framework only), a Windows Forms component that fires an event
    and executes the code in one or more event sinks at regular intervals.
    The component has no user interface and is designed for use in a
    single-threaded environment; it executes on the UI thread.
  • System.Web.UI.Timer
    (.NET Framework only), an ASP.NET component that performs asynchronous
    or synchronous web page postbacks at a regular interval.

It is interesting to mention that System.Timers.Timer was deprecated with .NET Core 1.0, but was implemented again in .NET Core 2.0 (/ .NET Standard 2.0).
The goal with .NET Standard 2.0 was that it should be as easy as possible to switch from the .NET Framework which is probably the reason it came back.

When it was deprecated, the .NET Portability Analyzer Visual Studio Add-In recommended to use System.Threading.Timer instead.

Looks like that Microsoft favors System.Threading.Timer before System.Timers.Timer.

EDIT NOTE 2018-11-15:
I had to change my answer since the old information about .NET Core 1.0 was not valid anymore.

Tolga's user avatar

Tolga

2,2431 gold badge24 silver badges18 bronze badges

answered Jun 21, 2016 at 19:39

ice1e0's user avatar

ice1e0ice1e0

9397 silver badges15 bronze badges

5

One important difference not mentioned above which might catch you out is that System.Timers.Timer silently swallows exceptions, whereas System.Threading.Timer doesn’t.

For example:

var timer = new System.Timers.Timer { AutoReset = false };
timer.Elapsed += (sender, args) =>
{
    var z = 0;
    var i = 1 / z;
};
timer.Start();

vs

var timer = new System.Threading.Timer(x =>
{
    var z = 0;
    var i = 1 / z;
}, null, 0, Timeout.Infinite);

answered Apr 4, 2016 at 20:28

stovroz's user avatar

stovrozstovroz

6,7172 gold badges47 silver badges59 bronze badges

4

I found a short comparison from MSDN

The .NET Framework Class Library includes four classes named Timer,
each of which offers different functionality:

System.Timers.Timer, which fires an event and executes the code in one or more event sinks at regular intervals. The class is intended
for use as a server-based or service component in a multithreaded
environment; it has no user interface and is not visible at runtime.

System.Threading.Timer, which executes a single callback method on a thread pool thread at regular intervals. The callback method is
defined when the timer is instantiated and cannot be changed. Like the
System.Timers.Timer class, this class is intended for use as a
server-based or service component in a multithreaded environment; it
has no user interface and is not visible at runtime.

System.Windows.Forms.Timer, a Windows Forms component that fires an event and executes the code in one or more event sinks at regular
intervals. The component has no user interface and is designed for use
in a single-threaded environment.

System.Web.UI.Timer, an ASP.NET component that performs asynchronous or synchronous web page postbacks at a regular interval.

answered Oct 2, 2014 at 14:36

default's user avatar

defaultdefault

11.3k8 gold badges66 silver badges102 bronze badges

From MSDN: System.Threading.Timer is a simple, lightweight timer that uses callback methods and is served by thread pool threads. It is not recommended for use with Windows Forms, because its callbacks do not occur on the user interface thread. System.Windows.Forms.Timer is a better choice for use with Windows Forms. For server-based timer functionality, you might consider using System.Timers.Timer, which raises events and has additional features.

Source

answered Mar 29, 2016 at 14:01

Greg Gum's user avatar

Greg GumGreg Gum

31.5k34 gold badges151 silver badges214 bronze badges

The two classes are functionally equivalent, except that System.Timers.Timer has an option to invoke all its timer expiration callbacks through ISynchronizeInvoke by setting SynchronizingObject. Otherwise, both timers invoke expiration callbacks on thread pool threads.

When you drag a System.Timers.Timer onto a Windows Forms design surface, Visual Studio sets SynchronizingObject to the form object, which causes all expiration callbacks to be called on the UI thread.

answered Sep 9, 2015 at 10:56

Edward Brey's user avatar

Edward BreyEdward Brey

39.4k19 gold badges193 silver badges247 bronze badges

As other mentioned the link to MS Docs, one major difference between System.Timers.Timer and System.Threading.Timer is that System.Threading.Timer executes a single callback method defined once while the System.Timers.Timer reacts on events, so supports multiple subscribers which can be also removed.

As also mentioned above, the System.Timers.Timer is using a System.Threading.Timer internally, with e.g. the Enable=false disposing the internal timer, and re creates it on Enable=true / Start():
https://source.dot.net/#System.ComponentModel.TypeConverter/System/Timers/Timer.cs

answered Jul 15, 2021 at 9:12

EricBDev's user avatar

EricBDevEricBDev

1,13711 silver badges18 bronze badges

Таймеры часто играют важную роль как в клиентских приложениях, так и в компонентах программ, основанных на серверах (включая службы 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 обработать инициализацию класса, либо можете инициализировать экземпляр класса таймера вручную, в Вашем коде.

Timers dot NET fig01

Рис. 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. Использование этого таймера несколько более сложное, однако предоставляет большую свободу, что Вы увидите дальше в этой статье.

Timers dot NET demo application fig02

Рис. 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.

Понравилась статья? Поделить с друзьями:
  • System thread exception windows 10 причина
  • System thread exception not handled windows перевод
  • System thread exception not handled windows netwtw04 system
  • System thread exception not handled windows 10 что это
  • System thread exception not handled windows 10 что делать при установке