关闭延迟定时器
Off-delay timer
有没有一种方法可以在 C# 中仅使用一个 System.Threading.Timer 对象来编写安全的一秒关闭延迟计时器 class?
或者,假设输入的打开和关闭速度比每秒一次快得多,通常最简单的解决方案是什么?
关闭延迟定时器可以用这个接口来描述:
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;
}
}
我可以看到此解决方案中存在竞争条件:
- 在一秒关闭期结束时,计时器将回调安排到 运行。
- 有人快速设置和重置输入,重新启动计时器。
- 回调现在终于 运行s,检查输入并将输出设置为 false,即使再过一秒它应该为 true。
编辑
Peter Duniho 提供了正确的答案,但事实证明我不善于提出正确的问题。当输出变为假时,OffDelay class 也应该做一些操作。这是适应彼得基本原理的修改后的代码:
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()
{
// ...
}
}
如果没有很好的 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()
, 并使用 Stopwatch
的 Elapsed
属性 来确定自上次输入以来的时间长度。
有没有一种方法可以在 C# 中仅使用一个 System.Threading.Timer 对象来编写安全的一秒关闭延迟计时器 class?
或者,假设输入的打开和关闭速度比每秒一次快得多,通常最简单的解决方案是什么?
关闭延迟定时器可以用这个接口来描述:
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;
}
}
我可以看到此解决方案中存在竞争条件:
- 在一秒关闭期结束时,计时器将回调安排到 运行。
- 有人快速设置和重置输入,重新启动计时器。
- 回调现在终于 运行s,检查输入并将输出设置为 false,即使再过一秒它应该为 true。
编辑
Peter Duniho 提供了正确的答案,但事实证明我不善于提出正确的问题。当输出变为假时,OffDelay class 也应该做一些操作。这是适应彼得基本原理的修改后的代码:
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()
{
// ...
}
}
如果没有很好的 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()
, 并使用 Stopwatch
的 Elapsed
属性 来确定自上次输入以来的时间长度。