26

ベース発行

C# アプリケーションのタイム スタンプに問題があります。リモート TCP 接続から非同期でデータを受信して​​います。データを受け取るたびに、タイムスタンプ変数を に更新しDateTime.Nowます。別のスレッドで、1 秒に 1 回、最後の受信から事前定義されたタイムアウト期間よりも長いかどうかを確認し、そうであれば切断します。この方法は長年機能してきましたが、現在、タイム ソースが不安定なマシンにアプリケーションがインストールされている状況があります。数日ごとに、マシンの時刻が「自動修正」され、途中で接続を切断します。コードは基本的に次のとおりです。

受信プロセス

void OnReceiveComplete(IAsyncResult ar) {
    ...
    mySocket.EndReceive(ar);
    lastRxTime = DateTime.Now;
    ...
}

チェックプロセス

void CheckConnection() {
    TimeSpan ts = DateTime.Now.Subtract(lastRxTime);
    if(ts.TotalMilliseconds > timeout) {
        Disconnect(string.Format("No packet received from the server for over {0} seconds.", timeout / 1000));
    }
}

問題発生中の有効な Wireshark キャプチャがあり、切断の直前に、少なくとも 1 分の修正のように見える NTP トラフィックが最高潮に達していることがわかります。これにより、明らかにチェック プロセスが失敗します。

技術的な詳細/予想される質問への回答

  1. 接続の両端を制御できますが、その間の物理層 (多くの場合、低品質の衛星リンク) は制御できません。これが、このタイムアウト チェックが行われている理由です。
  2. データは非同期であるため、タイムアウトの半分に等しい時間サーバーからデータが送信されない場合、小さなハートビートを送信するための規定があります。
  3. このプロセスの複数のインスタンスが存在する可能性があります (つまり、複数の異なるサーバーに接続しているマシン上で.
  4. すべての通信は非同期メソッドを使用します (これはおそらく完了ポートを使用します)。
  5. チェック プロセスは、単一のマシン上のすべてのクライアントによって共有される別のスレッドで実行されます。

完了した調査 (つまり、可能な解決策)

この時点までの私のGoogle検索は、次のことにつながりました。

  1. パフォーマンス上の理由から、DateTime.UtcNow代わりに使用する必要があったことに気づきました。DateTime.Nowこれは問題自体には影響しません。
  2. ティックに依存する実装は、より良い解決策です。
  3. ティックを取得するには 2 つのオプションがありますEnvironment.TickCountStopwatch.GetTimestamp()
  4. 私の調査によると、Environment.TickCount時間調整の影響を受けやすい可能性がありますが、どのような状況であるかはわかりません。また、私はこれと同じ方法論を他のより高いパフォーマンスの状況で使用しているため、10 ~ 16 ミリ秒の解像度が問題になる可能性があります (ただし、ここで提示している特定のケースではありません)。
  5. は、高性能クロックが利用できない場合にStopwatch.GetTimestamp()フォールバックできます。DateTime.Now.Ticksそれがどのくらいの頻度で発生するかはわかりませんが(高性能クロックが搭載されていないマシンはありますか)、Ticksに頼ると同じ問題が発生することは確かです.
  6. Stopwatch.GetTimestamp()API呼び出しを使用することも読みましたがQueryPerformanceCounter()、複数のスレッドから呼び出されると不安定になる可能性があります。

究極の質問

lastRxTimeタイムスタンプを生成する最良の方法は何ですか? Environment.TickCountおよびStopwatch.GetTimestamp()関数の可能性が低い問題について心配しすぎていませんか? アプリケーションのマルチスレッドの性質とリンクの品質の問題を考慮している限り、代替の実装を受け入れます。

UPDATE 7/17/2013 (解決策が展開されました!)

私はソリューションを展開しましたが、詳細については全員に説明したいと考えています。一般的に、受け入れられた答えは 1 つではありませんが、この経験を経て、元の解決策が間違いなく問題であったと言えます。できる限り詳細を提供するように努めます。

まず、NTP の問題は、実際には別の問題の兆候でした。問題が発生しているネットワークは、私のコードを実行している 2 台のサーバーがドメイン コントローラーとして設定されている AD ドメインです。DC はドメインのタイム ソースであることがわかります。また、システム時刻がこれらのシステムのリアルタイム クロックから約 11 日間にわたって最大 1 分ずれていることも判明しており、その時点で Windows はずれを修正しています。最初の DC のずれを修正すると、2 番目の DC が時刻を同期し、両方が上記の問題に遭遇します。

フィードバックと独自の調査に基づいて、切断中に実行して DateTime.Now、Environment.TickCount、および Stopwatch.GetTimestamp() の値を記録するテスト プログラムを作成しました。私が見つけたのは、修正中に Environment.TickCount も StopWatch.GetTimeStamp() もまったくずれていなかったことです。つまり、DateTime.Now() の代わりとして使用するのに適した候補でした。TickCount を使用したのは、デプロイされたすべてのサーバーに存在することが保証されているためです (ストップウォッチは、まだ見つけていない一部のマシンでは DateTime オブジェクトにフォールバックする可能性があります)。これまでのところ問題なく動作しています。フォームが問題になるのを防ぐために、ロールオーバーの問題について十分な注意を払いましたが、システムが稼働するまで待つ必要があります。

他の誰かが同様の問題を経験している場合、以下のリストに示されている他の解決策の使用を軽視してはならないことに注意してください. それぞれに独自のメリットがあります。実際、ほとんどの状況では、単純なカウンターが最適なソリューションである可能性があります。このソリューションに行かなかった理由は、タイトなタイミングに大きく依存する別の領域に同様のコードがあるためです。私はそこでティックカウントの16ミリ秒程度の解像度を処理できますが、カウンターソリューションが発生する時間のずれを処理できません(別の製品でそのようなコードを使用し、1時間に1秒以上ずれてしまい、私を連れてきましたプロジェクトの仕様外)。

繰り返しになりますが、すべてに感謝します。さらに質問があれば、必ず質問を更新します。

4

3 に答える 3

3

使用に問題はないと思いますEnvironment.TickCount。ドキュメントには、次のように返されると記載されています。

コンピューターが最後に起動されてから経過したミリ秒単位の時間を含む 32 ビットの符号付き整数。

続けて次のように述べています。

このプロパティの値は、システム タイマーから取得され、32 ビットの符号付き整数として格納されます。

NTP 時刻の修正がこれにどのように影響するかわかりません。そうでないことを示唆するリンクはありますか?トリッキーな部分は、ラップアラウンドの処理です。そのため、現在のティック カウントを以前のティック カウントと比較して、負の数を取得すると、ラップアラウンドが発生したことがわかります。

個人的には、これは使用する優れたメカニズムだと思います。戻り値はミリソン単位であるため、タイムアウトの変換係数は簡単です。それ以外の場合Stopwatch.GetTimestampは、ティック頻度を考慮するために追加の作業を行う必要があります。

さらに、これは Win32GetTickCount関数の呼び出しで内部的に実装されていると想定しています。(dotPeek は でマークされていることを示しているMethodImplOptions.InternalCallため、確かなことはわかりません)しかし、そのドキュメントには次のように記載されています。

備考

関数の分解能GetTickCountは、システム タイマーの分解能に制限されており、通常は 10 ミリ秒から 16 ミリ秒の範囲です。GetTickCount 関数の解像度は、関数による調整の影響を受けません GetSystemTimeAdjustment


よく考えてみると、1 秒ごとに定期的にチェックしているので、タイマーの分解能は 1 秒未満である必要があります。より良いものを作ろうとすることにゼロポイントがあるのに、そもそも API 呼び出しを使用する必要はありません。

private int m_conTimer;

void OneSecondThreadCallback() {
    if (++m_conTimer >= TIMEOUT_VALUE)
        // Connection timed out. React accordingly.
}
于 2013-06-13T00:01:32.863 に答える
3

DateTime.Nowこの目的で使用しない正当な理由がいくつかあります

  • あなたが指摘したように、パフォーマンスの問題があります。これは主に、ローカル タイム ゾーンに内部的に変換する必要があるためです。

  • 現地時間帯の「今」なので、比較や計算には不向きです。これは、夏時間への移行が発生するため、多くのタイム ゾーンで年に 2 回の不連続があるためです。前回のイベント時間と比較している場合、時計が進むと 1 時間余分に表示されます。ロールバックすると、0 から 60 分のどこかでオフになる可能性があります (正確に比較を行った時期によって異なります)。

  • Stopwatchorよりもはるかに精度が低くなりEnvironment.TickCountます。は非常に短い時間を表すことができますが、システム クロック自体は約 10 ~ 30 ミリ秒の精度しかありませDateTime。(しかし、おそらくそれはユースケースにとってそれほど重要ではありません。)

を使用DateTime.UtcNowすると、最初の 2 つのポイントに対処できますが、3 番目のポイントには対処できません。そのためには、完全に遠ざかる必要がありますDateTime

また、「クロックドリフト」という概念があります。クロックは、長期間にわたって遅くなったり速くなったりする場合があります。OS に時刻同期ソースがある場合、実際にはティックで実際に経過する時間を補正するために調整できます。詳細については、この記事を参照してください (「時間調整」セクションを参照してください)。

また、システム タイマーの解像度に関する情報を提供するために使用できるClockResユーティリティにも興味があるかもしれません。

ただし、あなたのユースケースはおそらく、これらの手法のいずれかを使用しない方がよいでしょう。タイムアウト期間後にイベントをキャンセルしたいとおっしゃいました。また、タイムアウト期間が経過したかどうかを定期的にチェックする別のスレッドがあるとも言いました。そのすべては不要です。代わりに、Timerクラスを使用してください。

.Netには3 つの異なるタイマーが用意されていることに注意してください。おそらく次のものが必要ですSystem.Threading.Timer

// start the timer, callback in 10000 milliseconds, and don't fire more than once
var timer = new Timer(TimerCallback, null, 10000, Timeout.Infinite);

// to reset the timer when you receive data
timer.Change(10000, Timeout.Infinite);

// to stop the timer completely
timer.Change(Timeout.Infinite, Timeout.Infinite);

// and in your callback
private void TimerCallback(object state)
{
    // disconnect, or do whatever you want
}

複数のスレッドからタイマーをリセットする可能性がある場合は、代わりにSystem.Timers.Timerスレッドセーフな - を使用する必要があります。

// to set up the timer
var timer = new Timer(10000) {AutoReset = false};
timer.Elapsed += TimerOnElapsed;

// to start the timer running
timer.Start();

// to reset the timer
timer.Stop();
timer.Start();

// and the callback
private void TimerOnElapsed(object sender, ElapsedEventArgs args)
{
    // disconnect, or do whatever you want
}
于 2013-06-13T00:31:38.270 に答える
2

Environment.TickCount および Stopwatch.GetTimestamp() 関数の可能性が低い問題について心配しすぎていませんか?

潜在的に、はい。

Stopwatch.GetTimestamp()私はオプションを好むでしょう。これは、システムで利用可能な場合は高性能タイマーを使用し、DateTime.Ticksそれがオプションでない場合はフォールバックします。そのため、特定のハードウェアで利用可能な場合は可能な限り最高のタイミングが得られ、高性能タイマーが利用できない場合のフォールバックとして適切なオプションが得られます。

興味深いオプションかもしれませEnvironment.TickCountんが、マシンが 24.9 日以上稼働する可能性がある場合、これがオーバーフローするケースを処理する必要があります。

于 2013-06-12T23:57:30.873 に答える