Таймеры C# проходят в отдельном потоке?

Происходит ли System.Timers.Timer в отдельном потоке, чем поток, который его создал?

Допустим, у меня есть класс с таймером, который срабатывает каждые 5 секунд. Когда таймер срабатывает, в прошедшем методе какой-то объект изменяется. Допустим, изменение этого объекта занимает много времени, например, 10 секунд. Возможно ли, что я столкнусь с коллизиями потоков в этом сценарии?

5 ответов

Решение

Для System.Timers.Timer:

Смотрите ответ Брайана Гидеона ниже

Для System.Threading.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. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

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

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Другие вопросы по тегам