15

在 C# (.NET 4.0) 应用程序中,我使用响应式扩展 (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
...

我也使用 a HistoricalScheduler,当然我在那里没有问题。我可以容忍轻微的不准确,我不需要关心系统时钟的变化。这些 Observable 不会触发重量级操作。

另外,我知道在这篇博文中对 RX 定时器漂移问题进行了冗长的讨论,但我似乎无法理解它。

定期安排Observable没有系统的计时器漂移的正确方法是什么?

4

2 回答 2

30

大多数机器上的默认 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 等等。

您实际上可以更改中断率,您可以调用 timeBeginPeriod() 并要求 1 毫秒的间隔。您需要 timeEndPeriod() 在程序终止时重置它。这并不是一件非常合理的事情,它具有系统范围的副作用并且对功耗非常不利。但是会解决你的问题。

一个更理智的方法是承认你永远无法通过增加间隔来准确地保持时间。CLR 使用的 15.6 毫秒已经是一个近似值。始终使用绝对时钟重新校准。通过要求 998 毫秒而不是 1000 毫秒来接近。等等。

于 2012-12-12T13:06:59.933 回答
12

您可以使用Observable.Generate

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).
于 2012-12-13T01:01:59.487 回答