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


98

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

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


Это могло привести к проблемам. Обратите внимание, что, как правило, потоки пула потоков не предназначены для длительных процессов.
— Greg D

Я столкнулся с этим, тестируя с помощью службы Windows. У меня сработало отключение таймера в качестве первой инструкции в событии OnTimer, выполнение моих задач, а затем включение таймера в конце. В течение некоторого времени это надежно работало в производственной среде.
— Стив

Ответы:


60

Для System.Timers.Timer :

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

Для System.Threading.Timer :

В документации MSDN по таймерам говорится:

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

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


21
Верно, но это совсем другой класс. ОП спросил о классе System.Timers.Timer.
— Брайан Гидеон

1
О, ты прав. msdn.microsoft.com/en-us/library/system.timers.timer.aspx сообщает: «Событие Elapsed возникает в потоке ThreadPool». Полагаю, такой же вывод отсюда.
— Joren

6
Ну да, но не все так просто. Смотрите мой ответ.
— Брайан Гидеон,

192

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

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

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


4
Если вы хотите, чтобы обратный вызов таймера выполнялся в новом потоке, следует ли использовать System.Threading.Timerили System.Timers.Timer?
— CJ7

1
@ cj7: Любой из них может это сделать.
— Брайан Гидеон

а если у меня есть список сложного типа (человек) и я хочу провести время внутри каждого человека? Мне нужно, чтобы это работало в одном потоке (все люди), потому что, если он вызывает метод от первого лица, второй человек должен ждать, пока первый не завершит прошедшее событие. Могу ли я сделать это?
— Леандро Де Мелло Фагундес,

4
У каждого ... System.Timers.Timerесть два режима работы. Он может работать в случайно назначенном потоке пула потоков ИЛИ в любом потоке, в котором размещен ISynchronizeInvokeэкземпляр. Я не знаю, как прояснить это. System.Threading.Timerимеет мало общего (если вообще есть) с исходным вопросом.
— Брайан Гидеон

@LeandroDeMelloFagundes Разве вы не можете использовать lockдля этого?
— Ozkan

24

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

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

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

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. Если удалить Thread.Sleep (2000); вы получите что-то вроде этого

10
6
6
6
6

Поскольку коллизий нет.

Но это по-прежнему оставляет вам проблему. если вы запускаете событие каждые 5 секунд, а редактирование занимает 10 секунд, вам нужна блокировка, чтобы пропустить некоторые изменения.


8
Добавление timer.Stop()в начало метода события Elapsed, а затемtimer.Start() в конец метода события Elapsed предотвратит столкновение события Elapsed.
— Metro Smurf

4
Вам не нужно ставить timer.Stop (), вам просто нужно определить timer.AutoReset = false; затем вы делаете timer.Start () после обработки события. Я думаю, это лучший способ избежать столкновений.
— João Antunes

17

Для 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 выполняется в потоке Workers.

Нет, дальнейшее усложнение - если вы уменьшите интервал до 10 мс,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

Это связано с тем, что если предыдущее выполнение OnDummyTimerFired не было выполнено при следующем запуске тика, .NET создаст новый поток для выполнения этой работы.

Еще больше усложняя ситуацию: «Класс System.Timers.Timer обеспечивает простой способ решения этой дилеммы - он предоставляет общедоступное свойство SynchronizingObject. Установка этого свойства в экземпляр Windows Form (или элемент управления в Windows Form) гарантирует что код в вашем обработчике событий Elapsed выполняется в том же потоке, в котором был создан экземпляр SynchronizingObject ".

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


Это хороший пример. Я не знал до сих пор, и мне нужно установить несколько замков здесь и там. Отлично.
— Олару Мирча

А что, если я хочу, чтобы таймер запускал событие Elapsed в основном потоке консольного приложения?
— jacktric

13

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

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}

+1 Простой и понятный ответ, но это не всегда сработает. Скорее следует использовать свойство lock или SynchronizingObject таймера.
— RollerCosta
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.