【问题标题】:Off-delay timer关闭延迟定时器
【发布时间】:2017-03-28 20:19:16
【问题描述】:

有没有办法在 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,即使它应该再为 true。

编辑

Peter Duniho 提供了正确的答案,但事实证明我不擅长提出正确的问题。当输出变为 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()
    {
        // ...
    }
}

【问题讨论】:

    标签: c# multithreading timer race-condition


    【解决方案1】:

    如果没有一个很好的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 实例,在每次更改Input 属性时调用Reset(),并使用@987654328 的Elapsed 属性@ 确定自上次输入以来的时间长度。

    【讨论】:

    • 我试图在发布的问题中避免不必要的复杂性,但我想我不小心遗漏了一个关键部分:当输出变为 false 时,我的班级需要做一些事情。不过,您的回答对于所提出的问题是正确的,它为我提供了真正解决方案所需的提示。
    猜你喜欢
    • 2011-07-06
    • 1970-01-01
    • 2013-07-25
    • 2020-01-16
    • 1970-01-01
    • 1970-01-01
    • 2015-12-14
    • 2021-06-14
    • 2017-01-29
    相关资源
    最近更新 更多