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

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

码:

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声明在线程池的单独线程中调用回调方法(每次定时器触发)。 这意味着如果我停止计时器方法中的第一件事它仍然不会阻止计时器触发并在第一个有机会停止计时器之前运行该方法的另一个实例。

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

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

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

几种可能的解决方案:

  • 在另一个正在等待事件的线程委托中完成真正的工作。 定时器回调仅仅表示事件。 工作线程无法重新进入,因为它是一个单独的线程,仅在发出事件信号时才能工作。 计时器是可重入的,因为它只是发出事件的信号(看起来有点迂回而浪费,但它会起作用)
  • 只使用启动超时创建计时器,没有周期性超时,因此它只会触发一次。 计时器回调将处理该计时器对象,并在完成其工作时创建一个新对象,该工作也只会触发一次。

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

编辑:


我做了测试 – 操作计时器作为可重启的一次性似乎完美地工作,并且它比其他方法简单得多。 这里有一些基于你的示例代码作为起点(一些细节可能已经改变,以便在我的机器上编译):

 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); } 

我与System.Timers.Timer有类似的情况,其中从线程池执行elapsed事件并且需要是可重入的。

我用这种方法解决了这个问题:

 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) 

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