【问题标题】:c# observable interval skips ticksc# 可观察间隔跳过刻度
【发布时间】:2015-05-22 07:33:02
【问题描述】:

在我的代码中,我需要有一个长时间运行的计时器来在每分钟的第一秒开始一些例程。我尝试使用 System.Timers.Timer,但由于计时器漂移,它不是很有用。所以我从 Reactive 扩展中实现了一个计时器,它每 200 毫秒计时一次,并在例程的开头放置了一些逻辑:

IObservable<Timestamped<long>> observable = Observable.Interval(TimeSpan.FromMilliseconds(200), Scheduler.NewThread).Timestamp();
IDisposable subscription = observable.Subscribe(x => calculator.Calculate(x.Timestamp));

然后在Calculate方法中:

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。

抱歉耽误您的时间。

【问题讨论】:

  • .Subscribe(...) 调用的签名与 Calculate 方法的签名不匹配。请提供minimal, complete, and verifiable代码好吗?
  • 对不起。应该是IDisposable subscription = observable.Subscribe(x =&gt; calculator.Calculate(x.Timestamp));
  • 您的代码仍然不完整。我正在尝试在 LINQPad 中运行它,但是缺少的项目太多。您能否发布将运行并产生结果的代码?
  • 我已经发布了代码。它一般工作。但我的问题是关于随机计时器故障。
  • 感谢您发布代码。现在在我看来,您是在告诉代码跳过秒为1 的所有时间戳。所以在你的问题中,当你说“整个 7:57:01 秒丢失”时,是因为你告诉它错过了。

标签: c# timer system.reactive


【解决方案1】:

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

在您的情况下,我认为您也可以这样做:不要使用秒数,而是使用毫秒数,或者,更好的是直接使用刻度,而不是与精确值相匹配,而是引入一些容差

【讨论】:

  • 感谢您的评论。但我已经使用了 200 毫秒的间隔。所以我每秒大约有 5 个计时器滴答声。
  • 问题的根源是你比较精确匹配,减少间隔本身并不能解决它
  • DateTimeOffset.Second 是整数,而不是双精度数。因此,将其与 1 进行比较应该没有问题。关键是,我记录了计算方法的每一步(在第二个!= 1 时跳过,在上次计算后的间隔和计算本身时跳过),但由于未知原因,很少有计数器步骤丢失。 7:57:00 到 7:57:02 之间的日志中没有任何内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多