c#可观察的间隔跳过刻度

本文关键字:观察 | 更新日期: 2023-09-27 17:50:19

在我的代码中,我需要有一个长运行计时器来启动一些例程的每一分钟的第一秒。我试过使用System.Timers。定时器,但它不是很有用,因为定时器漂移。所以我从响应扩展中实现了一个计时器,它每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。

对不起,占用您的时间了。

c#可观察的间隔跳过刻度

使用精确匹配匹配基于计时器的值本质上是不可靠的,因为不可预测的舍入误差。这与处理浮点数非常相似。你不应该做x == 1.0。你应该做的是abs(x-1.0) < 0.00001。这样,你就可以通过引入一些公差来消除小的舍入误差。

在你的情况下,我认为你可以做同样的事情:不是用秒来工作,而是用毫秒来工作,或者,更好的是直接用刻度来工作,而不是与精确的值进行比较,引入一些公差