当Enabled设置为false时,为什么System.Timer.Timer仍会触发事件?

时间:2011-06-23 14:08:07

标签: c# timer

我试图创建一个Timer的派生类,允许设置'Pause'锁存器以防止工作线程重新激活定时器。但是,当AutoReset设置为false时,继续引发Elapsed事件,并且Enabled访问器似乎正在执行它的工作,以防止在设置Paused变量后修改基类的Enabled属性。

为什么会发生这种情况或我应该采用什么策略来进一步了解这里实际发生的交互?

我已经在下面附加了派生类的实现。

    using System.Timers
    class PauseableTimer : Timer
    {
      public bool Paused;
      new public bool Enabled
      { 
        get
        {
          return base.Enabled;
        } 
        set
        {
          if (Paused)
          {
            if (!value) base.Enabled = false;
          } 
          else
          {
            base.Enabled = value;
          }
        }
      }
    }

说明问题的示例。

class Program
{
    private static PauseableTimer _pauseableTimer;
    private static int _elapsedCount;
    static void Main(string[] args)
    {
        _pauseableTimer = new PauseableTimer(){AutoReset = false,Enabled = false,Paused = false};

        _pauseableTimer.Elapsed += pauseableTimer_Elapsed;
        _pauseableTimer.Interval = 1;
        _pauseableTimer.Enabled = true;
        while(_elapsedCount<100)
        {
            if (_elapsedCount > 50) _pauseableTimer.Paused = true;
        }
    }

    static void pauseableTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(String.Format("this.Enabled:'{0}',Paused:'{1}',AutoReset:'{2}",_pauseableTimer.Enabled,_pauseableTimer.Paused,_pauseableTimer.AutoReset));
        _elapsedCount++;
        _pauseableTimer.Interval = _pauseableTimer.Interval == 1 ? 2 : 1; //This line breaks it.
        _pauseableTimer.Enabled = true;
    }
}

5 个答案:

答案 0 :(得分:7)

Relevant document, System.Timers.Timer.Interval

  

请注意   如果Enabled和AutoReset都设置为false,并且先前已启用计时器,则设置Interval属性会导致Elapsed事件被提升一次,就好像Enabled属性已设置为true一样。要在不引发事件的情况下设置间隔,可以暂时将AutoReset属性设置为true。

AutoReset设置为true的推荐解决方案无法解决问题,因为在事件处理程序中还存在将AutoReset设置为true的未记录行为,同时允许触发事件。

解决方案似乎是将派生对象构建到可以保持事件再次发生的任何显然方式的任何方面。

以下是我结束的实施。

public class PauseableTimer : Timer
{
    private bool _paused;
    public bool Paused
    {
        get { return _paused; }
        set 
        { 
            Interval = _interval;
            _paused = value;
        }
    }

    new public bool Enabled
    {
        get
        {
            return base.Enabled;
        }
        set
        {
            if (Paused)
            {
                if (!value) base.Enabled = false;
            }
            else
            {
                base.Enabled = value;
            }
        }
    }

    private double _interval;
    new public double Interval
    {
        get { return base.Interval; }
        set
        {
            _interval = value;
            if (Paused){return;}
            if (value>0){base.Interval = _interval;}
        }
    }

    public PauseableTimer():base(1){}

    public PauseableTimer(double interval):base(interval){}
}

答案 1 :(得分:3)

多线程中的一切都比较复杂,我担心。假设您的代码正常工作,有一个窗口可以在重置Enabled属性后引发正在进行的事件。请参阅MSDN docs中的这句话。

  

提升Elapsed事件的信号   总是排队等待执行   ThreadPool线程。这可能会导致   在Elapsed事件被提出   Enabled属性设置为后   假。 Stop的代码示例   方法显示了一种解决方法   这种竞争条件。

答案 2 :(得分:2)

另一种选择是压制事件???我无法解释发生了什么,但下面提出的理论应该可以让你避开你所讨论的这个小问题。正如史蒂夫提到的那样,在启用的属性上设置一个“监视和断点”,你可以尝试设置并确保它实际上已被设置。

我该如何解决这个问题:

捕获并检查'Enabled'属性并在需要时删除' - ='订阅方法(处理程序),然后在需要处理'Elapsed'事件时再次重新添加'+ ='。

我在几个不同的WinForms项目上使用过这种风格很多次。如果您不希望以编程方式处理'Elapsed'事件,则在满足某个条件时创建检查并将其删除,然后在满足相反条件时添加它。

if (paused) // determine pause logic to be true in here
{
   timer.Elapsed -= ... // remove the handling method.
}
else
{
   timer.Elapsed += ... // re-add it in again
}

上面的代码逻辑将允许您在'Paused'标志为真时,代码忽略'Elapsed'事件。我希望以上帮助

答案 3 :(得分:0)

在System.Timers.Timer中,创建类时,Elapsed事件将添加到ThreadPool中。之后将其解雇。那时Enabled属性可以为false。您对此无能为力,但是您可以做的是测试Elapsed事件触发时Enabled属性是否为true。我确实重写了Enabled属性,以使这种神奇效果发生,另外我还在其中添加了一个IsDisposed属性:

    public class DisposableTimer : System.Timers.Timer {

    /// <summary>
    ///     override the Timer base class Enabled property
    /// </summary>
    /// <remarks>
    ///     the code in the Elapsed event should only be executed when the Enabled property is set to "true".
    ///     we cannot prevent that the Elapsed event is fired at the start, because its automatically put in the ThreadPool,
    ///     but we can prevent that the code in it can be executed when the Enabled property is "false".
    /// </remarks>
    private bool enabled;
    public new bool Enabled 
    {
        get
        {
            return enabled;
        }

        set
        {
            enabled = base.Enabled = value;
        }
    }

    /// <summary>
    ///     count the heartbeats
    /// </summary>
    public int HeartbeatCounter { get; set; }

    /// <summary>
    ///     name of timer
    /// </summary>
    public string TimerName { get; set; }

    /// <summary>
    ///     show heartbeat on console
    /// </summary>
    public bool ShowHeartBeat { get; set; }

    // type of entry in eventlog
    public EventLogEntryType EventLogEntryType { get; set; }

    // updated interval to process messages
    public Func<double> UpdatebleInterval { get; set; }

    /// <summary>
    ///     used eventlog
    /// </summary>
    public EventLog EventLog { get; set; }

    /// <summary>
    ///     message logging 
    /// </summary>
    /// <remarks>
    ///     this property needs to be dynamic because in 
    ///     pda service a different class is used but luckily :-)
    ///     with the same method for adding loggings.
    /// </remarks>
    public dynamic MessageLogging { get; set; }

    /// <summary>
    ///     make sure there are no overlapping set of timer callbacks
    /// </summary>
    private object locker; 

    /// <summary>
    ///     initialize timer class
    /// </summary>
    /// <param name="actions">action to perform</param>
    /// <param name="timerName">name of timer</param>
    public DisposableTimer(List<Action> actions, string timerName) : base() 
    {
        // used to make sure there are no overlapping executing sets of timer callbacks
        locker = new object();

        // wrapper for the actions that need to be performed.
        base.Elapsed += (s, a) => Callback(actions);

        // set name of timer
        this.TimerName = timerName;

        /* 
         * only once a callback is executed after elapsed time,
         * because there is only one callback executed there can be
         * no overlap, because the "reset" is done after the set of
         * callbacks are executed.
         */
        AutoReset = false;

        // timer is not started yet
        Enabled = false;
    }

    /// <summary>
    ///     check if verwijder bericht timer is disposed
    /// </summary>
    public bool IsDisposed
    {
        get
        {
            var timerType = typeof(System.Timers.Timer);
            var timerDisposedField = timerType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
            return (bool)timerDisposedField.GetValue(this);
        }
    }

    /// <summary>
    ///     after a callback a timer needs to be reset to continue running if AutoReset=false.
    /// </summary>
    /// <param name="interval">new interval of timer</param>
    private void Reset(double interval)
    {
        // stop the timer
        Stop();

        // only do when not disposed yet.
        if (!IsDisposed)
        {
            // adjust interval if needed
            if (interval != 0)
                Interval = interval;

            // release exclusive lock
            Monitor.Exit(locker);
        }

        // start the timer again
        Start();
    }

    /// <summary>
    ///     only start if not disposed and started yet
    /// </summary>
    public new void Start()
    {
        if (!IsDisposed && !Enabled)
            Enabled = true;
    }

    /// <summary>
    ///     only stop if not disposed and stopped yet
    /// </summary>
    public new void Stop()
    {
        if (!IsDisposed && Enabled)
            Enabled = false;
    }

    /// <summary>
    ///     set of callbacks to perform after timer elapse interval
    /// </summary>
    /// <param name="callBackActions">list of callbacks inside this wrapper to execute</param>
    public void Callback(List<Action> callBackActions)
    {
        // only execute callbacks if timer is enabled.
        if (Enabled)
        {
            /*
             * AutoReset = false, so a callback is only executed once,
             * because of this overlapping callbacks should not occur,
             * but to be sure exclusive locking is also used.
             */
            var hasLock = false;

            // show heartbeat at output window
            if (ShowHeartBeat)
                Debug.WriteLine(string.Format("HeartBeat interval: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId() ));

            // execute callback action.
            try
            {
                // only perform set of actions if not executing already on this thread.
                Monitor.TryEnter(locker, ref hasLock);
                if (hasLock)
                {
                    // show heartbeat at output window
                    if (ShowHeartBeat)
                        Debug.WriteLine(string.Format("            action: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));

                    // execute the set of callback actions
                    foreach (Action callBackAction in callBackActions)
                    {
                        // execute callback 
                        try
                        {
                            callBackAction();
                        }

                        // log error, but keep the action loop going.
                        catch (Exception ex)
                        {
                            EventLog.WriteEntry(ex.Message, EventLogEntryType.Warning);
                            MessageLogging.Insert(ex.Message);
                        }
                    }
                }

                // show that action is busy
                else if (ShowHeartBeat)
                    Debug.WriteLine(string.Format("              busy: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));
            }

            // adjust interval when needed and release exclusive lock when done.
            finally
            {
                // after the complete action is finished the lock should be released.
                if (hasLock)
                {
                    // timer interval can be changed when timer is active, callback function is needed for this.
                    double newInterval = 0;
                    if (UpdatebleInterval != null)
                    {
                        // calculate new interval for timer
                        double updatedInterval = UpdatebleInterval();
                        if (Interval != updatedInterval)
                        {
                            // based on Dutch
                            var dutchCultureInfo = new CultureInfo("nl-NL", false).TextInfo;

                            // write interval change to loggings
                            string intervalMessage = dutchCultureInfo.ToTitleCase(string.Format(@"{0} interval veranderd van {1} naar {2} seconden", TimerName, Interval / 1000, updatedInterval / 1000));
                            EventLog.WriteEntry(intervalMessage, EventLogEntryType.Information);
                            MessageLogging.Insert(intervalMessage);

                            // set for new interval
                            newInterval = updatedInterval;
                        }
                    }

                    // make ready for new callback after elapsed time, lock can be released by now.
                    Reset(newInterval);
                }
            }
        }

        // show heartbeat at output window
        else if (ShowHeartBeat)
            Debug.WriteLine(string.Format("HeartBeat thread: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId()));
    }
}

答案 4 :(得分:-2)

我会重新格式化你的代码:

// from this
if (!value) base.Enabled = false;

// to this
if (!value) 
    base.Enabled = false;

它不仅读得更好,你可以在关键线上设置一个断点,看看它是否正在被执行