C# 在其回调方法中停止计时器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1699543/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 20:04:37  来源:igfitidea点击:

Stopping timer in its callback method

c#callbacktimerreentrancy

提问by Kornelije Petak

I have a System.Threading.Timer that calls its appropriate event handler (callback) every 10 ms. The method itself is not reentrantand can sometimes take way longer than 10 ms. Thus, I want to stop the timer during method execution.

我有一个 System.Threading.Timer 每10 ms调用一次其适当的事件处理程序(回调)。该方法本身是不可重入的,有时可能需要超过 10 ms 的时间。因此,我想在方法执行期间停止计时器。

Code:

代码:

private Timer _creatorTimer;

// BackgroundWorker's 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 states that the callback method is called (every time the timer fires) in separate thread from the thread pool. That means that if I stop the timer the first thing in method it still doesn't neccessarily prevent the timer to fire and run another instance of the method before the first one had a chance to stop the timer.

MSDN 声明回调方法在与线程池不同的线程中调用(每次计时器触发时)。这意味着,如果我在方法中的第一件事停止计时器,它仍然不一定阻止计时器在第一个有机会停止计时器之前触发并运行该方法的另一个实例。

Should maybe the timer (or even the non-reentrant method itself) be locked? What is the right way to prevent timer from firing during execution of its callback (and non-reentrant) method?

计时器(甚至非重入方法本身)是否应该被锁定?防止计时器在其回调(和不可重入)方法执行期间触发的正确方法是什么?

采纳答案by jsw

You could let the timer continue firing the callback method but wrap your non-reentrant code in a Monitor.TryEnter/Exit. No need to stop/restart the timer in that case; overlapping calls will not acquire the lock and return immediately.

您可以让计时器继续触发回调方法,但将您的不可重入代码包装在 Monitor.TryEnter/Exit 中。在这种情况下无需停止/重新启动计时器;重叠调用不会获取锁并立即返回。

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

回答by ParmesanCodice

I've had similar situation with a System.Timers.Timer, where the elapsed event is executed from a threadpool and needs to be reentrant.

我在 System.Timers.Timer 中遇到过类似的情况,其中经过的事件是从线程池执行的并且需要可重入。

I used this method to get around the issue:

我用这个方法来解决这个问题:

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

Depending on what you're doing you may want to consider a System.Timers.Timer, here's a nice summary from MSDN

根据您在做什么,您可能需要考虑 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)            

回答by Michael Burr

A couple possible solutions:

几个可能的解决方案:

  • have the real work done in yet another thread delegate that's waiting on an event. The timer callback merely signals the event. The worker thread cannot be reentered, as it's a single thread that does its work only when the event is signaled. The timer is reentrant, since all it does is signal the event (seems a little roundabout and wasteful, but it'll work)
  • have the timer created with only a start timeout and no periodic timeout so it'll fire only once. The timer callback will dispose of that timer object and create a new one when it has completed its work that will also only fire once.
  • 在另一个等待事件的线程委托中完成真正的工作。计时器回调仅发出事件信号。工作线程不能重新进入,因为它是一个仅在事件被发出信号时才执行其工作的单个线程。计时器是可重入的,因为它所做的只是向事件发出信号(似乎有点迂回和浪费,但它会起作用)
  • 创建的计时器只有一个开始超时,没有定期超时,所以它只会触发一次。计时器回调将处理该计时器对象并在它完成其工作后创建一个新的,该对象也只会触发一次。

You may be able to manage option #2 without disposing/creating a new object by using the Change()method of the original timer object, but I'm not sure what the behavior is exactly of calling Change()with a new start timeout after the first timeout has expired. That would be worth a test or two.

您可以通过使用Change()原始计时器对象的方法来管理选项 #2,而无需处理/创建新对象,但我不确定Change()在第一次超时到期后使用新的启动超时调用的行为究竟是什么. 那将值得一两次测试。

Edit:

编辑:



I did the test - manipulating the timer as a restartable one-shot seems to work perfectly, and it's much simpler than the other methods. Here's some sample code based on yours as a starting point (a few details may have changed to get it to compile on my machine):

我做了测试 - 将计时器作为可重新启动的单次操作来操作似乎很完美,而且它比其他方法简单得多。这是一些基于您的示例代码作为起点(一些细节可能已更改以使其在我的机器上编译):

private Timer _creatorTimer;

// BackgroundWorker's work
private void CreatorWork(object sender, EventArgs e) {
    // note: there's 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's start time, so it'll fire again
    //   there's no chance of reentrancy, except for actually
    //   exiting the method (and there's no danger even if that
    //   happens because it's safe at this point).
    _creatorTimer.Change(1000, Timeout.Infinite);
}

回答by ozba

I do it with Interlocked that provides atomic operations, and by CompareExchange ensures that only one thread at a time enters the critical section:

我使用提供原子操作的 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;
        }

    }

回答by BTE

    //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
    //