Содержание
В пространстве имен System.Threading
содержится класс Timer
, позволяющий повторять выполнение какой-либо задачи в потоке через равные промежутки времени. Рассмотрим несколько примеров использования таймеров в приложениях C#.
Использование класса Timer
В приведенном ниже примере каждую секунду в консоль выводится сообщение «Hello world»:
using System; using System.Threading; namespace Multithread { internal class Program { static void Main(string[] args) { Timer ts = new Timer(Callback, null, 500, 1000); Console.ReadLine(); } public static void Callback(object? obj) { Console.WriteLine("Hello world"); } } }
Рассмотрим этот пример подробнее. В начале мы создаем объект таймера, используя один из его конструкторов, который принимает четыре параметра:
- Метод обратного вызова (
Callback
). - Объект, используемый в методе обратного вызова или
null
, если такой объект не требуется; - Время в миллисекундах в течение которого таймер простаивает
- Интервал с которым будет срабатывать таймер и выполняться метод
Callback
.
Что касается метода обратного вызова, то при создании таймера можно использовать любой метод с сигнатурой, соответствующей делегату TimerCallback
, который выглядит следующий образом:
public delegate void TimerCallback(object? state);
Так как нам не требуется использовать в методе обратного вызова данные какого-либо объекта, то во втором параметре мы указали null
. Как только объект таймера создан, метод обратного вызова начинает выполняться, спустя время, указанное в третьем параметре конструктора, т.е. в нашем случае — это 0,5 секунды. В приведенном выше примере таймер будет срабатывать бесконечное количество раз.
Рассмотрим различные варианты использования таймера, а также те моменты, на которые стоит обратить внимание.
Использование объекта для метода обратного вызова в таймере
Второй параметр конструктора у Timer
принимает объект, который должен использоваться в методе обратного вызова. Учитывая то, что этот параметр имеет тип object
, мы можем передать в него всё, что захотим — строки, числа, объекты и т.д. То есть, по сути, такой подход является не типобезопасным, поэтому при использовании каких-либо объектов в методе обратного вызова стоит обращать внимание на то, что мы передаем в метод обратного вызова и то, как мы работаем с этим объектом в методе. Рассмотрим следующий пример:
using System; using System.Threading; namespace Multithread { public class Car { public int Velocity { get; set; } //скорость в м/с public double Distance { get; set; } //пройденное расстояние public Car(int velocity) { Velocity = velocity; } public void SetDistance(int timeInterval) { Distance += Velocity * (timeInterval / 1000); } } internal class Program { public static int time = 1000; static void Main(string[] args) { Car car = new Car(15); Timer ts = new Timer(Callback, car, 500, time); Console.ReadLine(); } public static void Callback(object? obj) { if ((obj != null) && (obj.GetType() == typeof(Car))) { ((Car)obj).SetDistance(time); Console.WriteLine($"Машина проехала {((Car)obj).Distance} метров"); } } } }
В этом примере мы используем для таймера объект типа Car
(машина) и через заданные промежутки времени рассчитываем какой путь прошла машина. Перед тем, как вызвать метод SetDistance
у машины мы в методе Callback
проверяем, чтобы объект не был равным null
и, при это, объект имел тип Car
, а не что-либо иное.
Прерывание работы таймера
В рассмотренных выше примерах метод обратного вызова будет срабатывать бесконечное количество раз до тех пор, пока мы не закроем приложение. Такое поведение таймера не всегда требуется и бывает необходимым прервать выполнение метода обратного вызова через некоторое время или при достижении определенного результата. Чтобы освободить ресурсы, занимаемые таймером, необходимо вызвать его метод Dispose
. К примеру, сделаем так, чтобы наша машина из предыдущего примера останавливалась после того, как проедет определенное расстояние:
using System; using System.Threading; namespace Multithread { public class Car { public int Velocity { get; set; } //скорость в м/с public double Distanse { get; set; } //пройденное расстояние public Car(int velocity) { Velocity = velocity; } public void SetDistanse(int timeInterval) { Distanse += Velocity * ((double)timeInterval / 1000); } } internal class Program { public static int time = 1000; public static Timer ts; static void Main(string[] args) { Car car = new Car(30); ts = new Timer(Callback, car, 500, time); Console.ReadLine(); } public static void Callback(object? obj) { if ((obj != null) && (obj.GetType() == typeof(Car))) { ((Car)obj).SetDistanse(time); Console.WriteLine($"Машина проехала {((Car)obj).Distanse} метров"); if (((Car)obj).Distanse >= 1000) { ts.Dispose(); //освобождаем ресурсы таймера Console.WriteLine("Машина приехала"); } } } } }
Здесь, в методе Callback
мы проверяем какое расстояние было пройдено и, при достижении расстояния 1000 или более метров вызываем метод Dispose
таймера, полностью прекращая тем самым его работу.
Изменение интервала срабатывания таймера
Для изменение интервала срабатывания таймера используется его метод Change
. Этот метод принимает два параметра:
- Время задержки срабатывания таймера
- Интервал срабатывания таймера
Например, такой код
ts.Change(0, 500)
приведет к тому, что таймер немедленно изменит свой интервал срабатывания на 500 мс (функция обратного вызова будет вызываться 2 раза в секунду). Чтобы предотвратить перезапуск таймера, в первом параметре необходимо указать бесконечность:
ts.Change(Timeout.Infinite, 500)
Соответственно, чтобы остановить работу таймера (но не освобождать, занимаемые им ресурсы) необходимо указать бесконечность во втором параметре метода Change:
ts.Change(0, Timeout.Infinite)
Итого
Сегодня мы рассмотрели работу с одним из таймеров в .NET, расположенном в пространстве имен System.Threading
. Timer
позволяет вызывать метод с заданным интервалом времени. В случае необходимости, мы можем либо полностью освободить ресурсы, занимаемые таймером, либо изменить интервал срабатывания таймера, используя его метод Change
.