Таймеры C# проходят в отдельном потоке?
Происходит ли System.Timers.Timer в отдельном потоке, чем поток, который его создал?
Допустим, у меня есть класс с таймером, который срабатывает каждые 5 секунд. Когда таймер срабатывает, в прошедшем методе какой-то объект изменяется. Допустим, изменение этого объекта занимает много времени, например, 10 секунд. Возможно ли, что я столкнусь с коллизиями потоков в этом сценарии?
5 ответов
Для System.Timers.Timer:
Смотрите ответ Брайана Гидеона ниже
Документация MSDN по таймерам гласит:
Класс System.Threading.Timer выполняет обратные вызовы в потоке ThreadPool и вообще не использует модель событий.
Таким образом, таймер действительно проходит в другом потоке.
Это зависит. System.Timers.Timer
имеет два режима работы.
Если SynchronizingObject
установлен на ISynchronizeInvoke
экземпляр тогда Elapsed
Событие будет выполнено в потоке, в котором размещен синхронизирующий объект. Обычно это ISynchronizeInvoke
случаи не что иное, как старый Control
а также Form
случаи, с которыми мы все знакомы. Так что в этом случае Elapsed
событие вызывается в потоке пользовательского интерфейса, и оно ведет себя подобно System.Windows.Forms.Timer
, В противном случае, это действительно зависит от конкретного ISynchronizeInvoke
экземпляр, который был использован.
Если SynchronizingObject
является нулевым, то Elapsed
событие вызывается на ThreadPool
нить и ведет себя подобно System.Threading.Timer
, На самом деле, он на самом деле использует System.Threading.Timer
негласно и выполняет операцию маршалинга после получения обратного вызова таймера, если это необходимо.
Каждое прошедшее событие будет запускаться в том же потоке, если предыдущее прошедшее событие все еще не запущено.
Так что он обрабатывает столкновение для вас
попробуйте положить это в консоль
static void Main(string[] args)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
var timer = new Timer(1000);
timer.Elapsed += timer_Elapsed;
timer.Start();
Console.ReadLine();
}
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Thread.Sleep(2000);
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
вы получите что-то вроде этого
10
6
12
6
12
где 10 - вызывающий поток, а 6 и 12 - из события bg elapsed. Если вы удалите Thread.Sleep(2000); вы получите что-то вроде этого
10
6
6
6
6
Так как нет столкновений.
Но это все еще оставляет вас с проблемой. если вы запускаете событие каждые 5 секунд, и редактирование занимает 10 секунд, вам нужна блокировка, чтобы пропустить некоторые изменения.
Для System.Timers.Timer, в отдельном потоке, если SynchronizingObject не установлен.
static System.Timers.Timer DummyTimer = null;
static void Main(string[] args)
{
try
{
Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);
DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
DummyTimer.Enabled = true;
DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
DummyTimer.AutoReset = true;
DummyTimer.Start();
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message);
}
return;
}
static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
return;
}
Вывод, который вы увидите, если DummyTimer сработает с интервалом в 5 секунд:
Main Thread Id: 9
12
12
12
12
12
...
Итак, как видно, OnDummyTimerFired выполняется в рабочем потоке.
Нет, дальнейшее осложнение - если вы уменьшите интервал до 10 мс,
Main Thread Id: 9
11
13
12
22
17
...
Это связано с тем, что если предварительное выполнение OnDummyTimerFired не было выполнено во время запуска следующего тика, тогда.NET создаст новый поток для выполнения этой работы.
Ситуация усложняется далее: "Класс System.Timers.Timer предоставляет простой способ решения этой дилеммы - он предоставляет открытое свойство SynchronizingObject. Установка этого свойства для экземпляра формы Windows (или элемента управления в форме Windows) обеспечит что код в вашем обработчике событий Elapsed выполняется в том же потоке, в котором был создан экземпляр SynchronizingObject. "
Если истекшее событие занимает больше времени, чем интервал, он создаст другой поток, чтобы вызвать истекшее событие. Но есть обходной путь для этого
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
timer.Stop();
Thread.Sleep(2000);
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
finally
{
timer.Start();
}
}