在我的代码中,我需要一个长时间运行的计时器来每隔一分钟开始一些例程。我试图使用System.Timers.Timer,但由于定时器漂移,它不是很有用。所以我已经实现了一个来自Reactive扩展的计时器,每200ms就会计时一次,并在例程的开头加上一些逻辑:
IObservable<Timestamped<long>> observable = Observable.Interval(TimeSpan.FromMilliseconds(200), Scheduler.NewThread).Timestamp();
IDisposable subscription = observable.Subscribe(x => calculator.Calculate(x.Timestamp));
然后在计算方法中:
public void Calculate(DateTimeOffset timeElapsed)
{
if (timeElapsed.Second != 1)
{
Log.Trace("Skip calc: second != 1. {0}", timeElapsed);
return;
}
if ((timeElapsed.LocalDateTime - lastRun).TotalSeconds < 59)
{
Log.Trace("Skip calc: interval < 60sec.");
return;
}
lastRun = timeElapsed.LocalDateTime;
var longRunningTask = new Task(() => CalcRoutine(timeElapsed), token);
longRunningTask.Start();
//etc..
}
问题是,有时没有任何可以理解的原因,这个计时器会跳过大约7个滴答。在这个特定情况下,在7:57:00丢失了2个最后一个刻度,并且缺少整个7:57:01秒:
2015-05-22 07:56:59.1550|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00
2015-05-22 07:56:59.3578|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00
2015-05-22 07:56:59.5606|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00
2015-05-22 07:56:59.7634|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00
2015-05-22 07:56:59.9662|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00
2015-05-22 07:57:00.1534|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00
2015-05-22 07:57:00.3562|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00
2015-05-22 07:57:00.5590|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00
2015-05-22 07:57:02.1502|Skip calc: second != 1. 22.5.2015 7:57:02 +02:00
2015-05-22 07:57:03.3671|Skip calc: second != 1. 22.5.2015 7:57:03 +02:00
此行为不规律。它发生在一天一到三次的随机时间。那个时刻的CPU负载是正常的,没有尖峰。磁盘也可以。我无法在计算机上重复它。此外,还有另一个相同应用的实例,其工作量较少,而且工作完全正常。应用程序每天午夜重新启动。
可能导致此问题的原因是什么?
UPD :完整代码
static void Main(string[] args)
{
var calculatorReact = new Calculator();
IObservable<Timestamped<long>> observable = Observable.Interval(TimeSpan.FromMilliseconds(200)).Timestamp();
IDisposable subscription = observable.Subscribe(x => calculatorReact.Calculate(x.Timestamp));
Console.ReadLine();
}
public class Calculator
{
DateTime lastRun = DateTime.Now.AddDays(-1);
public void Calculate(DateTimeOffset timeElapsed)
{
//start calcuation on the 1st second of every minute
if (timeElapsed.Second != 1)
{
Console.WriteLine("Skip calc: second != 1. {0}", timeElapsed);
return;
}
if ((timeElapsed.LocalDateTime - lastRun).TotalSeconds < 59)
{
Console.WriteLine("Skip calc: interval < 60sec.");
return;
}
lastRun = timeElapsed.LocalDateTime;
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
var longRunningTask = new Task(() => {
Console.WriteLine("Calulating..");
}, token);
longRunningTask.Start();
}
}
UPD2 问题在于该服务器上的时间同步。出于某些内部原因,我们不得不使用我们的定制软件,当发现差异时,它会快速转换系统时钟。因此它可以轻松地将时间从7:57:00转移到7:57:02。
抱歉花时间。
答案 0 :(得分:0)
由于不可预测的舍入误差,使用精确匹配匹配基于计时器的值本质上是不可靠的。这与使用浮点数非常相似。你永远不应该做x == 1.0
。你应该做的是abs(x-1.0) < 0.00001
。通过这种方式,您可以通过引入一些公差来消除图片中的小舍入误差。
在你的情况下,我认为你可以做同样的事情:而不是使用秒工作毫秒,或者更好,但直接用滴答,而不是comapring到精确值引入一些容差