Таймеры C # проходят в отдельном потоке? - PullRequest
83 голосов
/ 17 сентября 2009

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

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

Ответы [ 5 ]

179 голосов
/ 17 сентября 2009

Это зависит. System.Timers.Timer имеет два режима работы.

Если для SynchronizingObject установлено значение ISynchronizeInvoke, то событие Elapsed будет выполнено в потоке, в котором находится синхронизирующий объект. Обычно эти ISynchronizeInvoke экземпляры являются не чем иным, как простыми старыми Control и Form экземплярами, с которыми мы все знакомы. Таким образом, в этом случае событие Elapsed вызывается в потоке пользовательского интерфейса, и оно ведет себя подобно System.Windows.Forms.Timer. Иначе, это действительно зависит от конкретного экземпляра ISynchronizeInvoke, который был использован.

Если SynchronizingObject равен нулю, то событие Elapsed вызывается в потоке ThreadPool, и оно ведет себя подобно System.Threading.Timer. Фактически, он на самом деле использует System.Threading.Timer за кадром и выполняет операцию маршалинга после , при необходимости он получает обратный вызов таймера.

54 голосов
/ 17 сентября 2009

Для System.Timers.Timer :

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

Для System.Threading.Timer :

Документация MSDN по таймерам состояния:

Класс System.Threading.Timer составляет обратные вызовы в потоке ThreadPool и не использует модель событий вообще.

Так что таймер действительно проходит в другом потоке.

20 голосов
/ 17 сентября 2009

Каждое прошедшее событие будет запускаться в одном и том же потоке, если только не прошло предыдущее прошедшее.

Так что он обрабатывает столкновение для вас

попробуйте поместить это в консоль

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

13 голосов
/ 13 мая 2014

Для 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#S2

12 голосов
/ 20 октября 2009

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

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...