15

C#(.NET 4.0)アプリケーションでは、Reactive Extensions(2.0.20823.0)を使用して、イベントを集計値にグループ化するための時間境界を生成します。結果のデータベースへのクエリを単純化するには、これらの境界を1時間(または以下の例では秒)に揃える必要があります。

使用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て、もちろん問題ありません。わずかな不正確さは許容でき、システムクロックの変更を気にする必要はありません。これらのオブザーバブルによってトリガーされる重い操作はありません。

また、このブログ投稿でRXタイマーのドリフトの問題について長い議論があることは知っていますが、頭を悩ませることはできないようです。

Observable体系的なタイマードリフトなしで定期的にスケジュールする正しい方法は何でしょうか?

4

2 に答える 2

30

ほとんどのマシンのデフォルトのWindowsクロック割り込みレートは、1秒あたり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ミリ秒は、すでに概算です。常に絶対クロックで再校正してください。1000ではなく998ミリ秒を要求して近づきます。Etcetera。

于 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 に答える