不可重入的计时器

我有一个函数,我想每x秒调用一次,但我希望它是线程安全的。

我在创建计时器时可以设置此行为吗? (我不介意我使用哪个.NET计时器,我只是希望它是线程安全的)。

我知道我可以在我的回调函数中实现锁,但我认为如果它在计时器级别会更优雅。

我的回调函数和环境与UI无关。

[编辑1]我只是不希望我的回调函数中有多个线程。

[编辑2]我想将锁定保持在计时器级别内,因为计时器负责何时调用我的回调,这里有一种特殊情况,当我不想调用我的回调函数时。 所以我觉得什么时候打电话是计时器的责任

我猜测,因为你的问题并不完全清楚,你想要确保你的计时器在处理回调时无法重新输入你的回调,而你想要在没有锁定的情况下这样做。 您可以使用System.Timers.Timer实现此目的,并确保将AutoReset属性设置为false。 这将确保您必须手动触发每个间隔的计时器,从而防止任何重入:

 public class NoLockTimer : IDisposable { private readonly Timer _timer; public NoLockTimer() { _timer = new Timer { AutoReset = false, Interval = 1000 }; _timer.Elapsed += delegate { //Do some stuff _timer.Start(); // <- Manual restart. }; _timer.Start(); } public void Dispose() { if (_timer != null) { _timer.Dispose(); } } } 

作为Tim Lloyd的System.Timers.Timer解决方案的补充,这里有一个解决方案,可以防止您想要使用System.Threading.Timer情况进行重入。

 TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1); TimeSpan interval = TimeSpan.FromSeconds(1); Timer timer = null; // assign null so we can access it inside the lambda timer = new Timer(callback: state => { doSomeWork(); try { timer.Change(interval, DISABLED_TIME_SPAN); } catch (ObjectDisposedException timerHasBeenDisposed) { } }, state: null, dueTime: interval, period: DISABLED_TIME_SPAN); 

我相信你不希望在回调内部访问interval ,但如果你想这样做很容易解决:将上面的内容放入包装BCL的Timer类的NonReentrantTimer类中。 然后,您将doSomeWork回调作为参数传递。 这样一个类的一个例子:

 public class NonReentrantTimer : IDisposable { private readonly TimerCallback _callback; private readonly TimeSpan _period; private readonly Timer _timer; public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { _callback = callback; _period = period; _timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN); } private void Callback(object state) { _callback(state); try { _timer.Change(_period, DISABLED_TIME_SPAN); } catch (ObjectDisposedException timerHasBeenDisposed) { } } public void Dispose() { _timer.Dispose(); } } 

我知道我可以在我的回调函数中实现锁定,但我认为如果它在定时器级别会更优雅

如果需要锁定那么计时器如何安排呢? 你正在寻找一个神奇的免费赠品。

重新编辑1:

您的选择是System.Timers.Timer和System.Threading.Timer,两者都需要防止重新进入。 请参阅此页面并查找“ 处理计时器事件重入”部分。

 ///  /// The C#6 version ///  public class TimerNicer : IDisposable { private Action OnElapsed { get; } [NotNull] private System.Timers.Timer Timer { get; } = new System.Timers.Timer { AutoReset = false, Interval = 1 }; public TimerNicer(Action onElapsed) { this.OnElapsed = onElapsed ?? ( () => { } ); this.Timer.Elapsed += (sender, args) => { this.Timer.Stop(); // Why not stop the timer here with this? try { this.OnElapsed(); // do stuff here } catch ( Exception exception ) { Console.WriteLine( exception ); } finally { this.Timer.Start(); } }; this.Timer.Start(); } public void Dispose() => this.Timer.Dispose(); } 

计时器如何知道您的共享数据?

定时器回调在某些ThreadPool线程上执行。 所以你将至少有2个主题:

  1. 创建和启动计时器的主线程;
  2. ThreadPool中的线程用于启动回调。

您有责任正确处理共享数据。

重新编辑: chibacity提供了一个完美的例子。