System.Threading.Timer у C #, схоже, не працює. Він працює дуже швидко кожні 3 секунди


112

У мене об’єкт таймера. Я хочу, щоб це працювало щохвилини. Зокрема, він повинен запускати OnCallBackметод і стає неактивним під час роботи OnCallBackметоду. Після OnCallBackзакінчення методу він (a OnCallBack) перезапускає таймер.

Ось що я зараз маю:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

Однак, схоже, це не працює. Він працює дуже швидко кожні 3 секунди. Навіть коли підняти період (1000 * 10). Схоже, це закриває очі1000 * 10

Що я зробив не так?


12
З Timer.Change: "Якщо dueTime дорівнює нулю (0), метод зворотного виклику викликається негайно." Схоже, це для мене нуль.
Damien_The_Unbeliever

2
Так, але що ж? також є період.
Алан Коромано

10
То що робити, якщо є і період? У цитованому реченні жодних претензій щодо вартості періоду немає. Він просто говорить "якщо це значення дорівнює нулю, я негайно викликаю зворотний виклик".
Damien_The_Unbeliever

3
Цікаво, що якщо встановити як dueTime, так і період на 0, таймер буде працювати щосекунди і починати негайно.
Кельвін

Відповіді:


230

Це не правильне використання System.Threading.Timer. Коли ви створюєте таймер, ви майже завжди повинні робити наступне:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

Це доручить таймеру поставити галочку лише один раз, коли пройде інтервал. Потім у своїй функції зворотного виклику ви змінюєте таймер після завершення роботи, а не раніше. Приклад:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

Таким чином, немає необхідності в механізмах блокування, оскільки немає одночасності. Таймер запустить наступний зворотний виклик після закінчення наступного інтервалу + час тривалої операції.

Якщо вам потрібно запустити таймер рівно за N мілісекунд, то я пропоную вам виміряти час тривалої операції за допомогою секундоміра, а потім зателефонувати відповідним методом Change:

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

Я настійно заохочую всіх, хто робить .NET, і використовує CLR, який не читав книгу Джефрі Ріхтера - CLR через C # , щоб прочитати її якнайшвидше. Таймери та басейни з нитками пояснюються там дуже детально.


6
Я не згоден з цим private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackможе бути викликано ще раз до завершення операції.
Алан Коромано

2
Я мав на увазі те, що тоді Long running operationможе знадобитися набагато більше часу TIME_INTERVAL_IN_MILLISECONDS. Що було б тоді?
Алан Коромано

32
Зворотний виклик не буде викликаний повторно, в цьому справа. Тому ми передаємо Timeout.Infinite як другий параметр. Це, по суті, означає, що не відмічайте ще раз таймер. Потім відкладіть відмітку після того, як ми закінчили операцію.
Іван Златанов

Новачок тут для нарізування різьби - чи вважаєте ви, що це можливо зробити з a ThreadPool, якщо ви перейдете в таймер? Я думаю про сценарій, коли новий потік породжується, щоб виконувати роботу через певний проміжок часу, а потім повертається до пулу потоків після завершення.
jedd.ahyoung

2
System.Threading.Timer - це таймер пулу потоків, тобто виконує його зворотні виклики в пулі потоків, а не виділений потік. Після того, як таймер завершить процедуру зворотного виклику, потік, який виконав зворотний виклик, повертається в пул.
Іван Златанов

14

Не потрібно зупиняти таймер, дивіться приємне рішення з цієї публікації :

"Ви можете дозволити таймеру продовжувати запускати метод зворотного дзвінка, але загортати код, який не реєструється, у Monitor.TryEnter / Exit. У цьому випадку не потрібно зупиняти / перезапускати таймер; виклики, що перекриваються, не отримають блокування та повернуться негайно."

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }

це не для мого випадку. Мені потрібно точно зупинити таймер.
Алан Коромано

Ви намагаєтесь не один раз запобігти введенню зворотного дзвінка? Якщо ні, чого ви намагаєтесь досягти?
Іван Леоненко

1. Не допускайте введення зворотного дзвінка більше одного разу. 2. Не допускати виконання занадто багато разів.
Алан Коромано

Саме це і робить. №2 не є великим накладним, якщо він повертається відразу після if, якщо об'єкт заблокований, особливо якщо у вас такий великий інтервал.
Іван Леоненко

1
Це не гарантує, що код буде викликаний не менше <інтервал> після останнього виконання (нова галочка таймера може бути запущена на мікросекунду після того, як попередній галочок відпустив замок). Це залежить, чи це сувора вимога чи ні (не зовсім зрозуміло з опису проблеми).
Марко Мп

9

Використовується System.Threading.Timerобов'язково?

Якщо ні, то System.Timers.Timerє зручні Start()та Stop()методи (і AutoResetвластивість, яку ви можете встановити на false, так що цей Stop()файл не потрібен, і ви просто зателефонуєте Start()після виконання).


3
Так, але це може бути реальною вимогою, або просто трапилося, що таймер був обраний, тому що саме той використовується найбільш часто. На жаль. NET має багато об'єктів таймеру, які перекриваються на 90%, але все ще (іноді тонко) відрізняються. Звичайно, якщо це вимога, це рішення взагалі не застосовується.
Marco Mp

2
Відповідно до документації : Клас Systems.Timer доступний лише у .NET Framework. Він не входить у стандартну бібліотеку .NET і не доступний на інших платформах, таких як .NET Core або Universal Windows Platform. На цих платформах, а також для переносимості всіх платформ .NET замість цього слід використовувати клас System.Threading.Timer.
NotAgain повідомляє, що повернеться Моніка

3

Я просто зробив би:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

І ігноруйте параметр періоду, оскільки ви намагаєтеся самостійно контролювати періодику.


Ваш вихідний код працює так само швидко , як це можливо, так як ви зберігаєте вказівки 0для dueTimeпараметра. Від Timer.Change:

Якщо dueTime дорівнює нулю (0), негайно викликається метод зворотного виклику.


2
Чи потрібно утилізувати таймер? Чому ви не використовуєте Change()метод?
Алан Коромано

21
Розміщувати таймер кожного разу абсолютно непотрібно і неправильно.
Іван Златанов

0
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

якщо вам подобається використовувати певну петлю всередині ...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.