c#observable interval跳过ticks

时间:2015-05-22 07:33:02

标签: c# timer system.reactive

在我的代码中,我需要一个长时间运行的计时器来每隔一分钟开始一些例程。我试图使用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。

抱歉花时间。

1 个答案:

答案 0 :(得分:0)

由于不可预测的舍入误差,使用精确匹配匹配基于计时器的值本质上是不可靠的。这与使用浮点数非常相似。你永远不应该做x == 1.0。你应该做的是abs(x-1.0) < 0.00001。通过这种方式,您可以通过引入一些公差来消除图片中的小舍入误差。

在你的情况下,我认为你可以做同样的事情:而不是使用秒工作毫秒,或者更好,但直接用滴答,而不是comapring到精确值引入一些容差