在C#(.NET 4.0)应用程序中,我使用Reactive Extensions(2.0.20823.0)生成时间边界,以便将事件分组为聚合值。为了简化对结果数据库的查询,这些边界需要在整个小时(或以下示例中的秒)内对齐。
使用Observable.Timer()
:
var time = DefaultScheduler.Instance;
var start = new DateTimeOffset(time.Now.DateTime, time.Now.Offset);
var span = TimeSpan.FromSeconds(1);
start -= TimeSpan.FromTicks(start.Ticks % 10000000);
start += span;
var boundary = Observable.Timer(start, span, time);
boundary.Select(i => start + TimeSpan.FromSeconds(i * span.TotalSeconds))
.Subscribe(t => Console.WriteLine("ideal: " + t.ToString("HH:mm:ss.fff")));
boundary.Select(i => time.Now)
.Subscribe(t => Console.WriteLine("actual: " + t.ToString("HH:mm:ss.fff")));
你可以看到,定时器滴答声的预期时间和实际时间相差很大:
ideal: 10:06:40.000
actual: 10:06:40.034
actual: 10:06:41.048
ideal: 10:06:41.000
actual: 10:06:42.055
ideal: 10:06:42.000
ideal: 10:06:43.000
actual: 10:06:43.067
actual: 10:06:44.081
ideal: 10:06:44.000
ideal: 10:06:45.000
actual: 10:06:45.095
actual: 10:06:46.109
ideal: 10:06:46.000
ideal: 10:06:47.000
actual: 10:06:47.123
actual: 10:06:48.137
ideal: 10:06:48.000
...
我也使用HistoricalScheduler
,当然我在那里没有问题。我可以容忍轻微的不准确,我不需要关心系统时钟的变化。这些Observables没有触发重量级操作。
此外,我知道在这篇博客文章中有关于RX定时器漂移问题的长时间讨论,但我似乎无法理解
在没有系统计时器漂移的情况下,定期调度Observable
的正确方法是什么?
大多数机器上默认的Windows时钟中断率为每秒64次中断。CLR四舍五入为15.6毫秒。这不是一个令人满意的数字,如果你要求间隔1000毫秒,就没有整数除数。最接近的匹配是64 x 15.6=998(太短)和65 x 15.6=1014毫秒。
这正是你所看到的,41.048-40.034=1.014。44.081-43.067=1.014,依此类推。
实际上,您可以更改中断速率,您可以pinvoketimeBeginPeriod()并要求1毫秒的间隔。在程序终止时,您需要timeEndPeriod()来重置它。这不是一个非常合理的做法,它有系统范围的副作用,并且对功耗非常不利。但会解决你的问题。
一个更理智的方法是承认你永远无法通过增加间隔来准确地保持时间。CLR使用的15.6毫秒已经是近似值。始终使用绝对时钟重新校准。通过请求998毫秒而不是1000毫秒来拉近距离。等等。
您可以使用Observable。生成:
var boundary = Observable.Generate(
0, _ => true, // start condition
i => ++i, // iterate
i => i, // result selector
i => start + TimeSpan.FromSeconds(i * span.TotalSeconds),
time);
这将根据每次迭代的绝对时间重新安排。
以下是一些示例输出:
actual: 01:00:44.003
ideal: 01:00:44.000
actual: 01:00:44.999
ideal: 01:00:45.000
actual: 01:00:46.012
ideal: 01:00:46.000
actual: 01:00:47.011
ideal: 01:00:47.000
actual: 01:00:48.011
ideal: 01:00:48.000
actual: 01:00:49.007
ideal: 01:00:49.000
actual: 01:00:50.009
ideal: 01:00:50.000
actual: 01:00:51.006
ideal: 01:00:51.000
我想,由于汉斯解释的原因,它并不完全匹配,但没有漂移。
编辑:
以下是RxSource 的一些评论
// BREAKING CHANGE v2 > v1.x - No more correction for time drift based on absolute time. This
// didn't work for large period values anyway; the fractional
// error exceeded corrections. Also complicated dealing with system
// clock change conditions and caused numerous bugs.
//
// - For more precise scheduling, use a custom scheduler that measures TimeSpan values in a
// better way, e.g. spinning to make up for the last part of the period. Whether or not the
// values of the TimeSpan period match NT time or wall clock time is up to the scheduler.
//
// - For more accurate scheduling wrt the system clock, use Generate with DateTimeOffset time
// selectors. When the system clock changes, intervals will not be the same as diffs between
// consecutive absolute time values. The precision will be low (1s range by default).