Таймер остановки в методе обратного вызова

У меня есть System.Threading.Timer, который вызывает соответствующий обработчик события (callback) каждый 10 мс. Сам метод не реентеративен и иногда может принимать путь дольше 10 мс. Таким образом, я хочу остановить таймер во время выполнения метода.

код:

private Timer _creatorTimer;

// BackgroundWorker work
private void CreatorWork(object sender, DoWorkEventArgs e) {
      _creatorTimer = new Timer(CreatorLoop, null, 0, 10);

      // some other code that worker is doing while the timer is active
      // ...
      // ...
}

private void CreatorLoop(object state) {
      // Stop timer (prevent reentering)
      _creatorTimer.Change(Timeout.Infinite, 0);

      /*
          ... Work here
      */

      // Reenable timer
      _creatorTimer.Change(10, 0);
} 

MSDN заявляет, что вызывается метод обратного вызова (каждый раз, когда таймер срабатывает) в отдельном потоке из пула потоков. Это означает, что если я остановлю таймер, первое, что нужно сделать в методе, оно по-прежнему не предотвратит запуск таймера и запуск другого экземпляра метода до того, как первый из них сможет остановить таймер.

Может ли быть заблокирован таймер (или даже сам нерентерентный метод)? Каков правильный способ избежать срабатывания таймера во время выполнения его метода обратного вызова (и не реентерабельного)?

Ответ 1

Вы можете позволить таймеру продолжить запуск метода обратного вызова, но оберните свой невозвратный код в Monitor.TryEnter/Exit. Нет необходимости останавливать/перезапускать таймер в этом случае; перекрывающиеся вызовы не получат блокировку и немедленно возвращаются.

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

Ответ 2

Несколько возможных решений:

  • выполняется реальная работа в другом делегате потока, ожидающем события. Обратный вызов таймера просто сигнализирует о событии. Рабочий поток не может быть повторно введен, поскольку это единственный поток, который выполняет свою работу только тогда, когда событие сигнализируется. Таймер реентерабелен, так как все, что он делает, сигнализирует о событии (кажется немного окольным и расточительным, но оно будет работать).
  • имеет таймер, созданный только с таймаутом старта и без периодического таймаута, чтобы он срабатывал только один раз. Обратный вызов таймера будет удалять этот объект таймера и создать новый, когда он завершит свою работу, которая также будет срабатывать только один раз.

Возможно, у вас есть возможность управлять опцией №2 без утилизации/создания нового объекта с помощью метода Change() исходного объекта-таймера, но я не уверен, что именно это поведение вызывает вызов Change() с помощью новый таймаут запуска после истечения первого таймаута. Это стоило бы теста или два.

Edit:


Я сделал тест - манипулирование таймером, поскольку перезапускаемый один выстрел, кажется, работает отлично, и это намного проще, чем другие методы. Вот пример кода, основанного на вашем, в качестве отправной точки (некоторые детали, возможно, изменились, чтобы заставить его скомпилировать на моей машине):

private Timer _creatorTimer;

// BackgroundWorker work
private void CreatorWork(object sender, EventArgs e) {
    // note: there only a start timeout, and no repeat timeout
    //   so this will fire only once
    _creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite);

    // some other code that worker is doing while the timer is active
    // ...
    // ...
}

private void CreatorLoop(object state) {
    Console.WriteLine( "In CreatorLoop...");
    /*
        ... Work here
    */
    Thread.Sleep( 3000);

    // Reenable timer
    Console.WriteLine( "Exiting...");

    // now we reset the timer start time, so it'll fire again
    //   there no chance of reentrancy, except for actually
    //   exiting the method (and there no danger even if that
    //   happens because it safe at this point).
    _creatorTimer.Change(1000, Timeout.Infinite);
}

Ответ 3

У меня была схожая ситуация с System.Timers.Timer, где прошедшее событие выполняется из threadpool и должно быть реентеративным.

Я использовал этот метод, чтобы обойти проблему:

private void tmr_Elapsed(object sender, EventArgs e)
{
    tmr.Enabled = false;
    // Do Stuff
    tmr.Enabled = true;
}

В зависимости от того, что вы делаете, вы можете рассмотреть System.Timers.Timer, здесь хорошее резюме из MSDN

                                         System.Windows.Forms    System.Timers         System.Threading  
Timer event runs on what thread?         UI thread               UI or worker thread   Worker thread
Instances are thread safe?               No                      Yes                   No
Familiar/intuitive object model?         Yes                     Yes                   No
Requires Windows Forms?                  Yes                     No                    No
Metronome-quality beat?                  No                      Yes*                  Yes*
Timer event supports state object?       No                      No                    Yes
Initial timer event can be scheduled?    No                      No                    Yes
Class supports inheritance?              Yes                     Yes                   No

* Depending on the availability of system resources (for example, worker threads)            

Ответ 4

Я делаю это с Interlocked, который обеспечивает атомарные операции, а CompareExchange гарантирует, что только один поток за один раз входит в критический раздел:

private int syncPoint = 0;

private void Loop()
    {
        int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
         //ensures that only one timer set the syncPoint to  1 from 0
        if (sync == 0)
        {
            try
            {
               ...
            }
            catch (Exception pE)
            {
               ...  
            }
            syncPoint = 0;
        }

    }

Ответ 5

    //using Timer with callback on System.Threading namespace
    //  Timer(TimerCallback callback, object state, int dueTime, int period);
    //      TimerCallback: delegate to callback on timer lapse
    //      state: an object containig information for the callback
    //      dueTime: time delay before callback is invoked; in milliseconds; 0 immediate
    //      period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable
    // EXCEPTIONS:
    //      ArgumentOutOfRangeException: negative duration or period
    //      ArgumentNullException: callback parameter is null 

    public class Program
    {
        public void Main()
        {
            var te = new TimerExample(1000, 2000, 2);
        }
    }

    public class TimerExample
    {
        public TimerExample(int delayTime, int intervalTime, int treshold)
        {
            this.DelayTime = delayTime;
            this.IntervalTime = intervalTime;
            this.Treshold = treshold;
            this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime);
        }

        public int DelayTime
        {
            get;
            set;
        }

        public int IntervalTime
        {
            get;
            set;
        }

        public Timer Timer
        {
            get;
            set;
        }

        public StateInfo SI
        {
            get;
            set;
        }

        public int Treshold
        {
            get;
            private set;
        }

        public void TimerCallbackWorker(object state)
        {
            var si = state as StateInfo;

            if (si == null)
            {
                throw new ArgumentNullException("state");
            }

            si.ExecutionCounter++;

            if (si.ExecutionCounter > this.Treshold)
            {
                this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
                Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold);
            }
            else
            {
                Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString());
            }
        }

        public class StateInfo
        {
            public int ExecutionCounter
            {
                get;
                set;
            }

            public DateTime LastRun
            {
                get
                {
                    return DateTime.Now;
                }
            }

            public override string ToString()
            {
                return this.LastRun.ToString();
            }
        }
    }

    // Result:
    // 
    //  1 lapse, Time 2015-02-13 01:28:39 AM
    //  2 lapse, Time 2015-02-13 01:28:41 AM
    //  -Timer stop, execution reached treshold 2
    //