【发布时间】: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 => calculator.Calculate(x.Timestamp)); -
您的代码仍然不完整。我正在尝试在 LINQPad 中运行它,但是缺少的项目太多。您能否发布将运行并产生结果的代码?
-
我已经发布了代码。它一般工作。但我的问题是关于随机计时器故障。
-
感谢您发布代码。现在在我看来,您是在告诉代码跳过秒为
1的所有时间戳。所以在你的问题中,当你说“整个 7:57:01 秒丢失”时,是因为你告诉它错过了。
标签: c# timer system.reactive