关闭延迟计时器

时间:2017-03-28 20:19:16

标签: c# multithreading timer race-condition

有没有办法在C#中使用一个System.Threading.Timer对象编写一个安全的一秒关闭延迟计时器类?

或者,一般来说最简单的解决方案是什么,假设输入可以比每秒一次更快地打开和关闭?

此接口可以描述断开延迟计时器:

public interface IOffDelay
{
    /// <summary>
    /// May be set to any value from any thread.
    /// </summary>
    bool Input { get; set; }

    /// <summary>
    /// Whenever Input is true, Output is also true.
    /// The Output is only false at startup
    /// or after the Input has been continuously off for at least 1 second.
    /// </summary>
    bool Output { get; }
}

这是我的第一次尝试:

public sealed class OffDelay : IOffDelay, IDisposable
{
    public OffDelay()
    {
        timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
    }

    public void Dispose()
    {
        timer.Dispose();
    }

    public bool Input
    {
        get
        {
            lock (locker)
                return _input;
        }
        set
        {
            lock (locker)
            {
                if (value == _input)
                    return;

                _input = value;

                if (_input == false)
                    timer.Change(1000, Timeout.Infinite);
                else
                {
                    _output = true;
                    timer.Change(Timeout.Infinite, Timeout.Infinite);
                }
            }
        }
    }
    private bool _input;

    public bool Output
    {
        get
        {
            lock (locker)
                return _output;
        }
    }
    private bool _output;

    private readonly Timer timer;
    private readonly object locker = new object();

    private void TimerCallback(object state)
    {
        lock (locker)
            _output = false;
    }
}

我可以看到此解决方案中存在竞争条件:

  1. 在一秒休息时间结束时,计时器会安排回调运行。
  2. 有人快速设置并重置输入,重新启动计时器。
  3. 现在最终运行回调,检查输入并将输出设置为false,即使它应该为真的另一秒。
  4. 修改

    彼得·杜尼霍提供了正确的答案,但事实证明我在提出正确的问题时非常糟糕。当输出变为false时,OffDelay类也应该执行一些操作。以下是修改后的代码,以适应彼得的基本原则:

    public sealed class OffDelay : IOffDelay, IDisposable
    {
        public OffDelay()
        {
            timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
        }
    
        public void Dispose()
        {
            timer.Dispose();
        }
    
        public bool Input
        {
            get
            {
                lock (locker)
                    return _input;
            }
            set
            {
                lock (locker)
                {
                    if (value == _input)
                        return;
    
                    _input = value;
    
                    if (_input == true)
                        _output = true;
                    else
                    {
                        stopwatch.Restart();
                        if (!timerRunning)
                            timer.Change(1000, Timeout.Infinite);
                    }
                }
            }
        }
        private bool _input;
    
        public bool Output
        {
            get
            {
                lock (locker)
                    return _output;
            }
        }
        private bool _output;
    
        private readonly object locker = new object();
        private readonly Timer timer;
        private readonly Stopwatch stopwatch = new Stopwatch();
        private bool timerRunning;
    
        private void TimerCallback(object state)
        {
            lock (locker)
            {
                if (_input == true)
                    timerRunning = false;
                else
                {
                    var remainingTimeMs = 1000 - stopwatch.ElapsedMilliseconds;
                    if (remainingTimeMs > 0)
                        timer.Change(remainingTimeMs, Timeout.Infinite);
                    else
                    {
                        _output = false;
                        timerRunning = false;
                        DoSomething();
                    }
                }
            }
        }
    
        private void DoSomething()
        {
            // ...
        }
    }
    

1 个答案:

答案 0 :(得分:2)

如果没有明确说明问题的好Minimal, Complete, and Verifiable code example,包括准确显示上下文是什么以及可能存在哪些约束,那么无法确定哪些答案对您有用。

但根据您在问题中包含的内容,我会改变您的实施方式,因此它不依赖于计时器:

public sealed class OffDelay : IOffDelay
{
    public bool Input
    {
        get { lock (locker) return _input; }
        set
        {
            lock (locker)
            {
                if (value == _input)
                    return;

                _input = value;
                _lastInput = DateTime.UtcNow;
            }
        }
    }
    private bool _input;

    public bool Output
    {
        get { lock (locker) return (DateTime.UtcNOw - _lastInput).TotalSeconds < 1; }
    }
    private DateTime _lastInput;
}

请注意,上述内容易受计算机时钟更改的影响。如果您需要独立于时钟工作,则可以将DateTime.UtcNow替换为Stopwatch实例,在每次更改Reset()属性时调用Input,然后使用Elapsed的{​​{1}}属性,用于确定自上次输入以来的时间长度。