29

ループで実行しているコードがあり、現在の時刻に基づいて状態を保存しています。場合によってはミリ秒単位の間隔になることもありますが、何らかの理由で、DateTime.Now は 2 ミリ秒または 3 ミリ秒後であっても、常に少なくとも 10 ミリ秒離れた値を返すようです。保存している状態は保存された時間に依存するため、これは大きな問題を引き起こします(たとえば、何かを記録する)

各値を 10 ミリ秒間隔で返す私のテスト コード:

public static void Main()
{
    var dt1 = DateTime.Now;
    System.Threading.Thread.Sleep(2);
    var dt2 = DateTime.Now;

    // On my machine the values will be at least 10 ms apart
    Console.WriteLine("First: {0}, Second: {1}", dt1.Millisecond, dt2.Millisecond);
}

ミリ秒までの正確な現在時刻を取得する方法に関する別の解決策はありますか?

誰かが Stopwatch クラスを見ることを提案しました。Stopwatch クラスは非常に正確ですが、プログラムの状態を保存するために必要な現在の時刻を教えてくれません。

4

7 に答える 7

43

不思議なことに、あなたのコードはWin7のクアッドコアで完全に正常に機能し、ほぼ毎回正確に2ミリ秒離れた値を生成します。

だから私はもっと徹底的なテストをしました。これが私の出力例ですThread.Sleep(1)DateTime.UtcNowこのコードは、ループ内の連続する呼び出し間のミリ秒数を出力します。

睡眠1

各行には100文字が含まれているため、「クリーンラン」での100ミリ秒の時間を表します。したがって、この画面は約2秒をカバーします。最長のプリエンプションは4msでした。さらに、すべての反復が正確に1ミリ秒かかったときに、約1秒続く期間がありました。これはほぼリアルタイムのOS品質です。1 :)

だから私は今回、もう一度試しましThread.Sleep(2)た:

睡眠2

繰り返しますが、ほぼ完璧な結果です。今回は各行の長さが200ミリ秒で、ほぼ3秒の長さの実行があり、ギャップは正確に2ミリ秒以外にはなりませんでした。

当然、次に確認するのは、DateTime.UtcNow私のマシンの実際の解像度です。これは、まったく眠っていない実行です。まったく変更されていない.場合はaが出力されますUtcNow

全く寝ていない

最後に、上記の結果を生成した同じマシン上でタイムスタンプが15ミリ秒離れているという奇妙なケースを調査しているときに、次の奇妙な出来事に遭遇しました。

ここに画像の説明を入力してください ここに画像の説明を入力してください

Windows APIにはtimeBeginPeriod、アプリケーションがタイマーの頻度を一時的に上げるために使用できるという関数があります。したがって、これがおそらくここで起こったことです。タイマー解像度の詳細なドキュメントは、 Hardware Dev Center Archive、特にTimer-Resolution.docx(Wordファイル)から入手できます。

結論:

  • DateTime.UtcNow 15msよりもはるかに高い解像度を持つことができます
  • Thread.Sleep(1) 正確に1ms眠ることができます
  • 私のマシンでは、UtcNow成長は一度に正確に1msずつ成長します(丸め誤差を与えるか取る-Reflectorはに除算があることを示していますUtcNow)。
  • すべてが15.6msベースの場合、プロセスを低解像度モードに切り替えることができ、1msスライスの高解像度モードにオンザフライで切り替えることができます。

コードは次のとおりです。

static void Main(string[] args)
{
    Console.BufferWidth = Console.WindowWidth = 100;
    Console.WindowHeight = 20;
    long lastticks = 0;
    while (true)
    {
        long diff = DateTime.UtcNow.Ticks - lastticks;
        if (diff == 0)
            Console.Write(".");
        else
            switch (diff)
            {
                case 10000: case 10001: case 10002: Console.ForegroundColor=ConsoleColor.Red; Console.Write("1"); break;
                case 20000: case 20001: case 20002: Console.ForegroundColor=ConsoleColor.Green; Console.Write("2"); break;
                case 30000: case 30001: case 30002: Console.ForegroundColor=ConsoleColor.Yellow; Console.Write("3"); break;
                default: Console.Write("[{0:0.###}]", diff / 10000.0); break;
            }
        Console.ForegroundColor = ConsoleColor.Gray;
        lastticks += diff;
    }
}

タイマーの解像度を変更する可能性のある文書化されていない関数が存在することが判明しました。詳細は調べていませんが、ここにリンクを貼ろうと思いましたNtSetTimerResolution

1もちろん、OSが可能な限りアイドル状態であり、4つのかなり強力なCPUコアを自由に使用できることを確認しました。4つのコアすべてを100%にロードすると、画像が完全に変化し、どこでも長いプリエンプションが発生します。

于 2011-02-10T21:23:50.900 に答える
10

ミリ秒を扱うときの DateTime の問題は、DateTime クラスが原因ではなく、CPU ティックとスレッド スライスに関係しています。基本的に、他のスレッドを実行できるようにするためにスケジューラによって操作が一時停止された場合、再開する前に少なくとも 1 タイム スライス待機する必要があります。これは、最新の Windows OS では約 15 ミリ秒です。したがって、この 15 ミリ秒未満の精度で一時停止しようとすると、予期しない結果が発生します。

于 2008-11-21T02:22:32.433 に答える
2

何かをする前に現在の時刻のスナップショットを撮れば、保存した時刻にストップウォッチを追加できますよね?

于 2008-11-21T01:58:43.450 に答える
1

正確な時間が本当に必要なのか、それとも十分な時間に増加する整数を加えただけなのかを自問する必要があります。

ミューテックス、select、poll、WaitFor* などの待機イベントの直後に now() を取得し、おそらくナノ秒の範囲または余裕がある場所にシリアル番号を追加することで、良いことを行うことができます。

また、rdtsc マシン命令 (一部のライブラリでは API ラッパーが提供されていますが、C# や Java でこれを実行するかどうかは不明です) を使用して、CPU から安価な時間を取得し、それを time from now() と組み合わせることもできます。rdtsc の問題は、速度のスケーリングを行うシステムでは、何をするのかまったく確信が持てないことです。また、かなり速く周回します。

于 2008-11-21T01:59:49.780 に答える
0

このタスクを 100% 正確に達成するために使用したのは、タイマー コントロールとラベルだけです。

コードはあまり説明を必要とせず、かなり単純です。グローバル変数:

int timer = 0;

これは tick イベントです:

private void timeOfDay_Tick(object sender, EventArgs e)
    {

        timeOfDay.Enabled = false;
        timer++;

        if (timer <= 1)
        {
            timeOfDay.Interval = 1000;
            timeOfDay.Enabled = true;             
            lblTime.Text = "Time: " + DateTime.Now.ToString("h:mm:ss tt");                  
            timer = 0;
        }


}

フォームのロードは次のとおりです。

private void DriverAssignment_Load(object sender, EventArgs e)
    {


        timeOfDay.Interval= 1;
        timeOfDay.Enabled = true;


}
于 2015-03-05T00:41:47.033 に答える
-4

DateTime.Now.Ticks を使用できます。MSDNの記事を読んでください。

「1 ティックは 100 ナノ秒または 1,000 万分の 1 秒を表します。」

于 2008-11-21T01:57:50.363 に答える