17

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

代码:

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

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

4

5 回答 5

47

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

 private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }
于 2009-11-09T07:37:10.497 回答
6

几个可能的解决方案:

  • 在另一个等待事件的线程委托中完成真正的工作。计时器回调仅发出事件信号。工作线程不能重新进入,因为它是一个单线程,仅在事件发出信号时才工作。计时器是可重入的,因为它所做的只是向事件发出信号(似乎有点迂回和浪费,但它会起作用)
  • 只创建一个开始超时且没有定期超时的计时器,因此它只会触发一次。计时器回调将处理该计时器对象并在完成其工作后创建一个新对象,该对象也只会触发一次。

您可以通过使用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);
}
于 2009-11-09T07:38:38.723 回答
0

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

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

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)            
于 2009-11-09T07:34:59.573 回答
0

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

    }
于 2012-06-05T21:39:25.477 回答
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
    // 
于 2014-09-18T09:48:49.657 回答